You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2020/09/12 12:38:53 UTC

[httpcomponents-core] branch master updated (e497fd0 -> d05d6a9)

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git.


    from e497fd0  Upgraded HttpCore version to 5.0.3-SNAPSHOT
     new 684d3c6  Upgraded project version to 5.1-alpha1-SNAPSHOT
     new 4e52bcf  Async connection listeners to support passing attachments to endpoints
     new 49591ef  Improved TLS configuration of async server endpoints
     new b44e1da  Deprecated SecurePortStrategy
     new c590320  HTTPCORE-628: do not encode blanks as + in URI query component
     new 51ff9cb  HTTPCORE-627: Adding query parameters causes removal of the scheme specific part of URI by URIBuilder (#196)
     new 2e92ba7  Moved generic token parser out of o.a.hc.core5.http.message to o.a.hc.core5.util package
     new fd335d2  Better parse and format methods for URIAuthority
     new 4fce010  HTTPCORE-642: Implement ConnectionFactory fluent builders
     new 9885d2c  RFC 3986 conformance: revised URI parsing and formatting; URLEncodedUtils deprecated in favor of WWWFormCodec
     new 33eb3b5  RFC 3986 conformance: support percent-encoded reserved characters in the host component; host component can be empty
     new ba87dac  HTTPCORE-643: Implement NullEntity for convenience (#209)
     new bf7759d  HTTPCORE-639: Add a configurable ResponseOutOfOrder strategy for DefaultBHttpClientConnection
     new 9dfd5f3  HTTPCORE-645: Chunked request streams reuse buffers between requests
     new fa128ec  HTTPCORE-645: Increase blocking default chunk size from 2 KiB to 8 KiB
     new 19dea0f  Use decimal numbers for endpoint/execution IDs
     new f9374c0  HTTPCORE-649: Implement ByteArrayBuffer.append(ByteBuffer)
     new d05d6a9  Updated project URL

The 18 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 httpcore5-h2/pom.xml                               |   4 +-
 .../apache/hc/core5/http2/hpack/HPackDecoder.java  |  18 +-
 .../apache/hc/core5/http2/hpack/HPackEncoder.java  |  11 +-
 .../nio/ClientHttpProtocolNegotiatorFactory.java   |  18 +-
 .../nio/ServerHttpProtocolNegotiatorFactory.java   |  27 +-
 .../impl/nio/bootstrap/H2ServerBootstrap.java      |   3 +-
 .../http2/ssl/ConscryptServerTlsStrategy.java      |  54 ++-
 .../hc/core5/http2/ssl/H2ServerTlsStrategy.java    |  58 ++-
 .../core5/http2/examples/H2FileServerExample.java  |   3 +-
 .../http2/examples/H2FullDuplexServerExample.java  |   3 +-
 .../hc/core5/http2/examples/H2GreetingServer.java  |   7 +-
 httpcore5-reactive/pom.xml                         |   4 +-
 .../examples/ReactiveFullDuplexServerExample.java  |   3 +-
 httpcore5-testing/pom.xml                          |   4 +-
 .../hc/core5/testing/framework/FrameworkTest.java  |   5 +-
 .../framework/TestingFrameworkRequestHandler.java  |   7 +-
 .../hc/core5/benchmark/BenchmarkToolTest.java      |   2 +-
 ...gResponseOutOfOrderStrategyIntegrationTest.java | 215 +++++++++++
 .../apache/hc/core5/testing/nio/H2AlpnTest.java    |   6 +-
 .../testing/nio/H2ProtocolNegotiationTest.java     |  19 +-
 .../nio/H2ServerAndMultiplexingRequesterTest.java  |  13 +-
 .../testing/nio/H2ServerAndRequesterTest.java      |  11 +-
 .../hc/core5/testing/nio/H2TLSIntegrationTest.java |  28 +-
 .../core5/testing/nio/Http1AuthenticationTest.java |  11 +-
 .../testing/nio/Http1ServerAndRequesterTest.java   |  15 +-
 .../core5/testing/nio/SecureAllPortsStrategy.java  |  43 ---
 .../core5/testing/reactive/ReactiveClientTest.java |   3 +-
 httpcore5/pom.xml                                  |   4 +-
 .../hc/core5/http/impl/bootstrap/AsyncServer.java  |  12 +-
 .../http/impl/bootstrap/HttpAsyncRequester.java    | 103 +++---
 .../core5/http/impl/bootstrap/HttpAsyncServer.java |  44 +++
 .../hc/core5/http/impl/io/BHttpConnectionBase.java |  14 +-
 .../hc/core5/http/impl/io/ChunkedOutputStream.java |  26 +-
 .../http/impl/io/DefaultBHttpClientConnection.java | 105 +++++-
 .../io/DefaultBHttpClientConnectionFactory.java    |  98 ++++-
 .../io/DefaultBHttpServerConnectionFactory.java    |  73 ++++
 .../hc/core5/http/impl/io/EmptyInputStream.java    |   2 +
 .../hc/core5/http/impl/io/IncomingHttpEntity.java  |   1 +
 .../io/MonitoringResponseOutOfOrderStrategy.java   | 111 ++++++
 ...tory.java => NoResponseOutOfOrderStrategy.java} |  35 +-
 ...-info.java => ResponseOutOfOrderException.java} |  15 +-
 .../http/impl/nio/ClientHttp1IOEventHandler.java   |   2 +
 .../impl/nio/ClientHttp1IOEventHandlerFactory.java |  13 +-
 ...onEventHandler.java => EndpointParameters.java} |  26 +-
 .../http/impl/nio/ServerHttp1IOEventHandler.java   |   5 +-
 .../impl/nio/ServerHttp1IOEventHandlerFactory.java |  26 +-
 .../core5/http/io/ResponseOutOfOrderStrategy.java  |  66 ++++
 .../hc/core5/http/io/entity/BasicHttpEntity.java   |   1 -
 .../{impl/io => io/entity}/EmptyInputStream.java   |   4 +-
 .../hc/core5/http/io/entity/EntityUtils.java       |   4 +-
 .../hc/core5/http/io/entity/HttpEntities.java      |   4 +-
 .../{HttpEntityWrapper.java => NullEntity.java}    |  92 ++---
 .../core5/http/message/BasicHeaderValueParser.java |  15 +-
 .../hc/core5/http/message/BasicLineParser.java     |  39 +-
 .../hc/core5/http/message/BasicTokenIterator.java  |   9 +-
 .../hc/core5/http/message/BufferedHeader.java      |   3 +-
 .../hc/core5/http/message/MessageSupport.java      |   5 +-
 .../apache/hc/core5/http/message/ParserCursor.java |  54 +--
 .../apache/hc/core5/http/message/TokenParser.java  | 217 +-----------
 .../http/nio/entity/AsyncEntityProducers.java      |   4 +-
 .../http/nio/entity/BasicAsyncEntityConsumer.java  |  11 +-
 .../core5/http/nio/ssl/BasicServerTlsStrategy.java |  60 +++-
 .../hc/core5/http/nio/ssl/FixedPortStrategy.java   |   3 +
 .../hc/core5/http/nio/ssl/SecurePortStrategy.java  |   3 +
 .../http/nio/support/AsyncRequestBuilder.java      |   4 +-
 .../java/org/apache/hc/core5/http/ssl/TLS.java     |   4 +-
 .../apache/hc/core5/http/ssl/TlsVersionParser.java |  13 +-
 .../java/org/apache/hc/core5/net/PercentCodec.java | 163 +++++++++
 .../java/org/apache/hc/core5/net/URIAuthority.java | 139 +++++---
 .../java/org/apache/hc/core5/net/URIBuilder.java   | 294 ++++++++++++---
 .../org/apache/hc/core5/net/URLEncodedUtils.java   | 304 ++--------------
 .../java/org/apache/hc/core5/net/WWWFormCodec.java |  83 +++++
 ...alDataChannelFactory.java => ChannelEntry.java} |  24 +-
 .../hc/core5/reactor/ConnectionAcceptor.java       |   3 +
 ...ectionAcceptor.java => ConnectionListener.java} |   7 +-
 .../core5/reactor/DefaultListeningIOReactor.java   |  23 +-
 .../org/apache/hc/core5/reactor/IOSessionImpl.java |   2 +-
 .../hc/core5/reactor/ListenerEndpointImpl.java     |   6 +-
 .../hc/core5/reactor/ListenerEndpointRequest.java  |   4 +-
 .../hc/core5/reactor/SingleCoreIOReactor.java      |  20 +-
 .../reactor/SingleCoreListeningIOReactor.java      |  33 +-
 .../org/apache/hc/core5/util/ByteArrayBuffer.java  |  16 +
 .../java/org/apache/hc/core5/util/Timeout.java     |   5 +
 .../TokenParser.java => util/Tokenizer.java}       | 111 +++++-
 .../hc/core5/http/NameValuePairListMatcher.java    |  85 +++++
 .../http/examples/AsyncFileServerExample.java      |   3 +-
 .../examples/AsyncFullDuplexServerExample.java     |   3 +-
 .../http/examples/AsyncReverseProxyExample.java    |   5 +-
 .../http/examples/AsyncServerFilterExample.java    |   3 +-
 .../TestMonitoringResponseOutOfOrderStrategy.java  | 133 +++++++
 .../core5/http/io/entity/TestBasicHttpEntity.java  |   1 -
 .../hc/core5/http/io/entity/TestEntityUtils.java   |   4 +-
 .../http/io/entity/TestInputStreamEntity.java      |   1 -
 .../hc/core5/http/io/entity/TestNullEntity.java    |  90 +++++
 .../entity/TestAbstractBinAsyncEntityConsumer.java |  11 +-
 .../http/protocol/TestStandardInterceptors.java    |   2 +-
 .../hc/core5/http/ssl/TestTlsVersionParser.java    |   7 +-
 .../org/apache/hc/core5/net/TestPercentCodec.java  |  70 ++++
 .../org/apache/hc/core5/net/TestURIAuthority.java  |  82 ++++-
 .../org/apache/hc/core5/net/TestURIBuilder.java    | 273 +++++++++++---
 .../apache/hc/core5/net/TestURLEncodedUtils.java   | 394 ---------------------
 .../org/apache/hc/core5/net/TestWWWFormCodec.java  | 123 +++++++
 .../apache/hc/core5/util/TestByteArrayBuffer.java  |  76 ++++
 .../TestTokenizer.java}                            |  47 ++-
 pom.xml                                            |   6 +-
 105 files changed, 3026 insertions(+), 1555 deletions(-)
 create mode 100644 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/MonitoringResponseOutOfOrderStrategyIntegrationTest.java
 delete mode 100644 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/SecureAllPortsStrategy.java
 create mode 100644 httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/MonitoringResponseOutOfOrderStrategy.java
 copy httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/{DefaultClassicHttpRequestFactory.java => NoResponseOutOfOrderStrategy.java} (62%)
 copy httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/{package-info.java => ResponseOutOfOrderException.java} (86%)
 copy httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/{HttpConnectionEventHandler.java => EndpointParameters.java} (68%)
 create mode 100644 httpcore5/src/main/java/org/apache/hc/core5/http/io/ResponseOutOfOrderStrategy.java
 copy httpcore5/src/main/java/org/apache/hc/core5/http/{impl/io => io/entity}/EmptyInputStream.java (97%)
 copy httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/{HttpEntityWrapper.java => NullEntity.java} (58%)
 create mode 100644 httpcore5/src/main/java/org/apache/hc/core5/net/PercentCodec.java
 create mode 100644 httpcore5/src/main/java/org/apache/hc/core5/net/WWWFormCodec.java
 copy httpcore5/src/main/java/org/apache/hc/core5/reactor/{InternalDataChannelFactory.java => ChannelEntry.java} (74%)
 copy httpcore5/src/main/java/org/apache/hc/core5/reactor/{ConnectionAcceptor.java => ConnectionListener.java} (91%)
 copy httpcore5/src/main/java/org/apache/hc/core5/{http/message/TokenParser.java => util/Tokenizer.java} (71%)
 create mode 100644 httpcore5/src/test/java/org/apache/hc/core5/http/NameValuePairListMatcher.java
 create mode 100644 httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMonitoringResponseOutOfOrderStrategy.java
 create mode 100644 httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestNullEntity.java
 create mode 100644 httpcore5/src/test/java/org/apache/hc/core5/net/TestPercentCodec.java
 delete mode 100644 httpcore5/src/test/java/org/apache/hc/core5/net/TestURLEncodedUtils.java
 create mode 100644 httpcore5/src/test/java/org/apache/hc/core5/net/TestWWWFormCodec.java
 rename httpcore5/src/test/java/org/apache/hc/core5/{http/message/TestTokenParser.java => util/TestTokenizer.java} (83%)


[httpcomponents-core] 09/18: HTTPCORE-642: Implement ConnectionFactory fluent builders

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 4fce010129b88362268c6acfc5beb403b425ef68
Author: Carter Kozak <c4...@gmail.com>
AuthorDate: Wed Jul 29 17:40:09 2020 -0400

    HTTPCORE-642: Implement ConnectionFactory fluent builders
    
    This closes #208
---
 .../io/DefaultBHttpClientConnectionFactory.java    | 66 +++++++++++++++++++
 .../io/DefaultBHttpServerConnectionFactory.java    | 73 ++++++++++++++++++++++
 2 files changed, 139 insertions(+)

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 e9781df..37c2e48 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
@@ -106,4 +106,70 @@ public class DefaultBHttpClientConnectionFactory
         return conn;
     }
 
+    /**
+     * Create a new {@link Builder}.
+     *
+     * @since 5.1
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link DefaultBHttpClientConnectionFactory}.
+     *
+     * @since 5.1
+     */
+    public static final class Builder {
+        private Http1Config http1Config;
+        private CharCodingConfig charCodingConfig;
+        private ContentLengthStrategy incomingContentLengthStrategy;
+        private ContentLengthStrategy outgoingContentLengthStrategy;
+        private HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory;
+        private HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory;
+
+        private Builder() {}
+
+        public Builder http1Config(final Http1Config http1Config) {
+            this.http1Config = http1Config;
+            return this;
+        }
+
+        public Builder charCodingConfig(final CharCodingConfig charCodingConfig) {
+            this.charCodingConfig = charCodingConfig;
+            return this;
+        }
+
+        public Builder incomingContentLengthStrategy(final ContentLengthStrategy incomingContentLengthStrategy) {
+            this.incomingContentLengthStrategy = incomingContentLengthStrategy;
+            return this;
+        }
+
+        public Builder outgoingContentLengthStrategy(final ContentLengthStrategy outgoingContentLengthStrategy) {
+            this.outgoingContentLengthStrategy = outgoingContentLengthStrategy;
+            return this;
+        }
+
+        public Builder requestWriterFactory(
+                final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory) {
+            this.requestWriterFactory = requestWriterFactory;
+            return this;
+        }
+
+        public Builder responseParserFactory(
+                final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) {
+            this.responseParserFactory = responseParserFactory;
+            return this;
+        }
+
+        public DefaultBHttpClientConnectionFactory build() {
+            return new DefaultBHttpClientConnectionFactory(
+                    http1Config,
+                    charCodingConfig,
+                    incomingContentLengthStrategy,
+                    outgoingContentLengthStrategy,
+                    requestWriterFactory,
+                    responseParserFactory);
+        }
+    }
 }
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 d74c528..8a67f1e 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
@@ -107,4 +107,77 @@ public class DefaultBHttpServerConnectionFactory implements HttpConnectionFactor
         return conn;
     }
 
+    /**
+     * Create a new {@link Builder}.
+     *
+     * @since 5.1
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link DefaultBHttpServerConnectionFactory}.
+     *
+     * @since 5.1
+     */
+    public static final class Builder {
+        private String scheme;
+        private Http1Config http1Config;
+        private CharCodingConfig charCodingConfig;
+        private ContentLengthStrategy incomingContentLengthStrategy;
+        private ContentLengthStrategy outgoingContentLengthStrategy;
+        private HttpMessageParserFactory<ClassicHttpRequest> requestParserFactory;
+        private HttpMessageWriterFactory<ClassicHttpResponse> responseWriterFactory;
+
+        private Builder() {}
+
+        public Builder scheme(final String scheme) {
+            this.scheme = scheme;
+            return this;
+        }
+
+        public Builder http1Config(final Http1Config http1Config) {
+            this.http1Config = http1Config;
+            return this;
+        }
+
+        public Builder charCodingConfig(final CharCodingConfig charCodingConfig) {
+            this.charCodingConfig = charCodingConfig;
+            return this;
+        }
+
+        public Builder incomingContentLengthStrategy(final ContentLengthStrategy incomingContentLengthStrategy) {
+            this.incomingContentLengthStrategy = incomingContentLengthStrategy;
+            return this;
+        }
+
+        public Builder outgoingContentLengthStrategy(final ContentLengthStrategy outgoingContentLengthStrategy) {
+            this.outgoingContentLengthStrategy = outgoingContentLengthStrategy;
+            return this;
+        }
+
+        public Builder requestParserFactory(
+                final HttpMessageParserFactory<ClassicHttpRequest> requestParserFactory) {
+            this.requestParserFactory = requestParserFactory;
+            return this;
+        }
+
+        public Builder responseWriterFactory(
+                final HttpMessageWriterFactory<ClassicHttpResponse> responseWriterFactory) {
+            this.responseWriterFactory = responseWriterFactory;
+            return this;
+        }
+
+        public DefaultBHttpServerConnectionFactory build() {
+            return new DefaultBHttpServerConnectionFactory(
+                    scheme,
+                    http1Config,
+                    charCodingConfig,
+                    incomingContentLengthStrategy,
+                    outgoingContentLengthStrategy,
+                    requestParserFactory,
+                    responseWriterFactory);
+        }
+    }
 }


[httpcomponents-core] 07/18: Moved generic token parser out of o.a.hc.core5.http.message to o.a.hc.core5.util package

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 2e92ba7c273561f843fdee7eab09ea9bd00fe0dd
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Fri Jul 24 11:13:32 2020 +0200

    Moved generic token parser out of o.a.hc.core5.http.message to o.a.hc.core5.util package
---
 .../core5/http/message/BasicHeaderValueParser.java |  15 +-
 .../hc/core5/http/message/BasicLineParser.java     |  39 ++--
 .../hc/core5/http/message/BasicTokenIterator.java  |   9 +-
 .../hc/core5/http/message/BufferedHeader.java      |   3 +-
 .../hc/core5/http/message/MessageSupport.java      |   5 +-
 .../apache/hc/core5/http/message/ParserCursor.java |  54 +----
 .../apache/hc/core5/http/message/TokenParser.java  | 217 ++-------------------
 .../java/org/apache/hc/core5/http/ssl/TLS.java     |   4 +-
 .../apache/hc/core5/http/ssl/TlsVersionParser.java |  13 +-
 .../org/apache/hc/core5/net/URLEncodedUtils.java   |   9 +-
 .../TokenParser.java => util/Tokenizer.java}       | 111 +++++++++--
 .../hc/core5/http/ssl/TestTlsVersionParser.java    |   7 +-
 .../TestTokenizer.java}                            |  47 +++--
 13 files changed, 189 insertions(+), 344 deletions(-)

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 4cb6ba5..b1a063c 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
@@ -36,6 +36,7 @@ import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.HeaderElement;
 import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.Tokenizer;
 
 /**
  * Default {@link org.apache.hc.core5.http.message.HeaderValueParser} implementation.
@@ -52,13 +53,13 @@ public class BasicHeaderValueParser implements HeaderValueParser {
 
     // IMPORTANT!
     // These private static variables must be treated as immutable and never exposed outside this class
-    private static final BitSet TOKEN_DELIMS = TokenParser.INIT_BITSET('=', PARAM_DELIMITER, ELEM_DELIMITER);
-    private static final BitSet VALUE_DELIMS = TokenParser.INIT_BITSET(PARAM_DELIMITER, ELEM_DELIMITER);
+    private static final BitSet TOKEN_DELIMS = Tokenizer.INIT_BITSET('=', PARAM_DELIMITER, ELEM_DELIMITER);
+    private static final BitSet VALUE_DELIMS = Tokenizer.INIT_BITSET(PARAM_DELIMITER, ELEM_DELIMITER);
 
-    private final TokenParser tokenParser;
+    private final Tokenizer tokenizer;
 
     public BasicHeaderValueParser() {
-        this.tokenParser = TokenParser.INSTANCE;
+        this.tokenizer = Tokenizer.INSTANCE;
     }
 
     @Override
@@ -94,7 +95,7 @@ public class BasicHeaderValueParser implements HeaderValueParser {
     public NameValuePair[] parseParameters(final CharSequence buffer, final ParserCursor cursor) {
         Args.notNull(buffer, "Char sequence");
         Args.notNull(cursor, "Parser cursor");
-        tokenParser.skipWhiteSpace(buffer, cursor);
+        tokenizer.skipWhiteSpace(buffer, cursor);
         final List<NameValuePair> params = new ArrayList<>();
         while (!cursor.atEnd()) {
             final NameValuePair param = parseNameValuePair(buffer, cursor);
@@ -112,7 +113,7 @@ public class BasicHeaderValueParser implements HeaderValueParser {
         Args.notNull(buffer, "Char sequence");
         Args.notNull(cursor, "Parser cursor");
 
-        final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS);
+        final String name = tokenizer.parseToken(buffer, cursor, TOKEN_DELIMS);
         if (cursor.atEnd()) {
             return new BasicNameValuePair(name, null);
         }
@@ -121,7 +122,7 @@ public class BasicHeaderValueParser implements HeaderValueParser {
         if (delim != '=') {
             return new BasicNameValuePair(name, null);
         }
-        final String value = tokenParser.parseValue(buffer, cursor, VALUE_DELIMS);
+        final String value = tokenizer.parseValue(buffer, cursor, VALUE_DELIMS);
         if (!cursor.atEnd()) {
             cursor.updatePos(cursor.getPos() + 1);
         }
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 9b2e805..04dd847 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
@@ -38,6 +38,7 @@ import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.util.Args;
 import org.apache.hc.core5.util.CharArrayBuffer;
 import org.apache.hc.core5.util.TextUtils;
+import org.apache.hc.core5.util.Tokenizer;
 
 /**
  * Default {@link org.apache.hc.core5.http.message.LineParser} implementation.
@@ -51,16 +52,16 @@ public class BasicLineParser implements LineParser {
 
     // IMPORTANT!
     // These private static variables must be treated as immutable and never exposed outside this class
-    private static final BitSet FULL_STOP = TokenParser.INIT_BITSET('.');
-    private static final BitSet BLANKS = TokenParser.INIT_BITSET(' ', '\t');
-    private static final BitSet COLON = TokenParser.INIT_BITSET(':');
+    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(':');
 
     /**
      * A version of the protocol to parse.
      * The version is typically not relevant, but the protocol name.
      */
     private final ProtocolVersion protocol;
-    private final TokenParser tokenParser;
+    private final Tokenizer tokenizer;
 
     /**
      * Creates a new line parser for the given HTTP-like protocol.
@@ -71,7 +72,7 @@ public class BasicLineParser implements LineParser {
      */
     public BasicLineParser(final ProtocolVersion proto) {
         this.protocol = proto != null? proto : HttpVersion.HTTP_1_1;
-        this.tokenParser = TokenParser.INSTANCE;
+        this.tokenizer = Tokenizer.INSTANCE;
     }
 
     /**
@@ -87,7 +88,7 @@ public class BasicLineParser implements LineParser {
         final String protoname = this.protocol.getProtocol();
         final int protolength  = protoname.length();
 
-        this.tokenParser.skipWhiteSpace(buffer, cursor);
+        this.tokenizer.skipWhiteSpace(buffer, cursor);
 
         final int pos = cursor.getPos();
 
@@ -112,7 +113,7 @@ public class BasicLineParser implements LineParser {
 
         cursor.updatePos(pos + protolength + 1);
 
-        final String token1 = this.tokenParser.parseToken(buffer, cursor, FULL_STOP);
+        final String token1 = this.tokenizer.parseToken(buffer, cursor, FULL_STOP);
         final int major;
         try {
             major = Integer.parseInt(token1);
@@ -125,7 +126,7 @@ public class BasicLineParser implements LineParser {
                     buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
         }
         cursor.updatePos(cursor.getPos() + 1);
-        final String token2 = this.tokenParser.parseToken(buffer, cursor, BLANKS);
+        final String token2 = this.tokenizer.parseToken(buffer, cursor, BLANKS);
         final int minor;
         try {
             minor = Integer.parseInt(token2);
@@ -150,20 +151,20 @@ public class BasicLineParser implements LineParser {
         Args.notNull(buffer, "Char array buffer");
 
         final ParserCursor cursor = new ParserCursor(0, buffer.length());
-        this.tokenParser.skipWhiteSpace(buffer, cursor);
-        final String method = this.tokenParser.parseToken(buffer, cursor, BLANKS);
+        this.tokenizer.skipWhiteSpace(buffer, cursor);
+        final String method = this.tokenizer.parseToken(buffer, cursor, BLANKS);
         if (TextUtils.isEmpty(method)) {
             throw new ParseException("Invalid request line",
                     buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
         }
-        this.tokenParser.skipWhiteSpace(buffer, cursor);
-        final String uri = this.tokenParser.parseToken(buffer, cursor, BLANKS);
+        this.tokenizer.skipWhiteSpace(buffer, cursor);
+        final String uri = this.tokenizer.parseToken(buffer, cursor, BLANKS);
         if (TextUtils.isEmpty(uri)) {
             throw new ParseException("Invalid request line",
                     buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
         }
         final ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
-        this.tokenParser.skipWhiteSpace(buffer, cursor);
+        this.tokenizer.skipWhiteSpace(buffer, cursor);
         if (!cursor.atEnd()) {
             throw new ParseException("Invalid request line",
                     buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
@@ -176,10 +177,10 @@ public class BasicLineParser implements LineParser {
         Args.notNull(buffer, "Char array buffer");
 
         final ParserCursor cursor = new ParserCursor(0, buffer.length());
-        this.tokenParser.skipWhiteSpace(buffer, cursor);
+        this.tokenizer.skipWhiteSpace(buffer, cursor);
         final ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
-        this.tokenParser.skipWhiteSpace(buffer, cursor);
-        final String s = this.tokenParser.parseToken(buffer, cursor, BLANKS);
+        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))) {
                 throw new ParseException("Status line contains invalid status code",
@@ -202,12 +203,12 @@ public class BasicLineParser implements LineParser {
         Args.notNull(buffer, "Char array buffer");
 
         final ParserCursor cursor = new ParserCursor(0, buffer.length());
-        this.tokenParser.skipWhiteSpace(buffer, cursor);
-        final String name = this.tokenParser.parseToken(buffer, cursor, COLON);
+        this.tokenizer.skipWhiteSpace(buffer, cursor);
+        final String name = this.tokenizer.parseToken(buffer, cursor, COLON);
         if (cursor.getPos() == cursor.getLowerBound() || cursor.getPos() == cursor.getUpperBound() ||
                 buffer.charAt(cursor.getPos()) != ':' ||
                 TextUtils.isEmpty(name) ||
-                TokenParser.isWhitespace(buffer.charAt(cursor.getPos() - 1))) {
+                Tokenizer.isWhitespace(buffer.charAt(cursor.getPos() - 1))) {
             throw new ParseException("Invalid header",
                     buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
         }
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 d58207b..45ca98d 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
@@ -32,6 +32,7 @@ import java.util.Iterator;
 
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.util.TextUtils;
+import org.apache.hc.core5.util.Tokenizer;
 
 /**
  * {@link java.util.Iterator} of {@link org.apache.hc.core5.http.Header} tokens..
@@ -40,9 +41,9 @@ import org.apache.hc.core5.util.TextUtils;
  */
 public class BasicTokenIterator extends AbstractHeaderElementIterator<String> {
 
-    private static final BitSet COMMA = TokenParser.INIT_BITSET(',');
+    private static final BitSet COMMA = Tokenizer.INIT_BITSET(',');
 
-    private final TokenParser parser;
+    private final Tokenizer tokenizer;
 
     /**
      * Creates a new instance of {@link BasicTokenIterator}.
@@ -51,12 +52,12 @@ public class BasicTokenIterator extends AbstractHeaderElementIterator<String> {
      */
     public BasicTokenIterator(final Iterator<Header> headerIterator) {
         super(headerIterator);
-        this.parser = TokenParser.INSTANCE;
+        this.tokenizer = Tokenizer.INSTANCE;
     }
 
     @Override
     String parseHeaderElement(final CharSequence buf, final ParserCursor cursor) {
-        final String token = this.parser.parseToken(buf, cursor, COMMA);
+        final String token = this.tokenizer.parseToken(buf, cursor, COMMA);
         if (!cursor.atEnd()) {
             final int pos = cursor.getPos();
             if (buf.charAt(pos) == ',') {
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 b4ff32d..6148e09 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
@@ -33,6 +33,7 @@ import org.apache.hc.core5.http.FormattedHeader;
 import org.apache.hc.core5.http.ParseException;
 import org.apache.hc.core5.util.Args;
 import org.apache.hc.core5.util.CharArrayBuffer;
+import org.apache.hc.core5.util.Tokenizer;
 
 /**
  * This class represents a raw HTTP header whose content is parsed 'on demand'
@@ -90,7 +91,7 @@ public class BufferedHeader implements FormattedHeader, Serializable {
         if (colon <= 0) {
             throw new ParseException("Invalid header", buffer, 0, buffer.length());
         }
-        if (strict && TokenParser.isWhitespace(buffer.charAt(colon - 1))) {
+        if (strict && Tokenizer.isWhitespace(buffer.charAt(colon - 1))) {
             throw new ParseException("Invalid header", buffer, 0, buffer.length(), colon - 1);
         }
         final String s = buffer.substringTrimmed(0, colon);
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 b875527..263cc2d 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
@@ -45,6 +45,7 @@ import org.apache.hc.core5.http.Method;
 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;
 
 /**
  * Support methods for HTTP message processing.
@@ -100,7 +101,7 @@ public class MessageSupport {
         return BufferedHeader.create(buffer);
     }
 
-    private static final BitSet COMMA = TokenParser.INIT_BITSET(',');
+    private static final BitSet COMMA = Tokenizer.INIT_BITSET(',');
 
     public static Set<String> parseTokens(final CharSequence src, final ParserCursor cursor) {
         Args.notNull(src, "Source");
@@ -111,7 +112,7 @@ public class MessageSupport {
             if (src.charAt(pos) == ',') {
                 cursor.updatePos(pos + 1);
             }
-            final String token = TokenParser.INSTANCE.parseToken(src, cursor, COMMA);
+            final String token = Tokenizer.INSTANCE.parseToken(src, cursor, COMMA);
             if (!TextUtils.isBlank(token)) {
                 tokens.add(token);
             }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/ParserCursor.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/ParserCursor.java
index fcbd2e4..dcf51df 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/ParserCursor.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/ParserCursor.java
@@ -27,7 +27,7 @@
 
 package org.apache.hc.core5.http.message;
 
-import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.Tokenizer;
 
 /**
  * This class represents a context of a parsing operation:
@@ -38,58 +38,10 @@ import org.apache.hc.core5.util.Args;
  *
  * @since 4.0
  */
-public class ParserCursor {
-
-    private final int lowerBound;
-    private final int upperBound;
-    private int pos;
+public class ParserCursor extends Tokenizer.Cursor {
 
     public ParserCursor(final int lowerBound, final int upperBound) {
-        super();
-        Args.notNegative(lowerBound, "lowerBound");
-        Args.check(lowerBound <= upperBound, "lowerBound cannot be greater than upperBound");
-        this.lowerBound = lowerBound;
-        this.upperBound = upperBound;
-        this.pos = lowerBound;
-    }
-
-    public int getLowerBound() {
-        return this.lowerBound;
-    }
-
-    public int getUpperBound() {
-        return this.upperBound;
-    }
-
-    public int getPos() {
-        return this.pos;
-    }
-
-    public void updatePos(final int pos) {
-        if (pos < this.lowerBound) {
-            throw new IndexOutOfBoundsException("pos: "+pos+" < lowerBound: "+this.lowerBound);
-        }
-        if (pos > this.upperBound) {
-            throw new IndexOutOfBoundsException("pos: "+pos+" > upperBound: "+this.upperBound);
-        }
-        this.pos = pos;
-    }
-
-    public boolean atEnd() {
-        return this.pos >= this.upperBound;
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder buffer = new StringBuilder();
-        buffer.append('[');
-        buffer.append(Integer.toString(this.lowerBound));
-        buffer.append('>');
-        buffer.append(Integer.toString(this.pos));
-        buffer.append('>');
-        buffer.append(Integer.toString(this.upperBound));
-        buffer.append(']');
-        return buffer.toString();
+        super(lowerBound, upperBound);
     }
 
 }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/TokenParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/TokenParser.java
index cdd6a21..e8dcbd0 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/TokenParser.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/TokenParser.java
@@ -31,8 +31,7 @@ import java.util.BitSet;
 
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
-import org.apache.hc.core5.http.Chars;
-import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.Tokenizer;
 
 /**
  * Low level parser for header field elements. The parsing routines of this class are designed
@@ -41,17 +40,14 @@ import org.apache.hc.core5.util.Args;
  * This class is immutable and thread safe.
  *
  * @since 4.4
+ *
+ * @deprecated Use {@link Tokenizer}
  */
+@Deprecated
 @Contract(threading = ThreadingBehavior.IMMUTABLE)
-public class TokenParser {
+public class TokenParser extends Tokenizer {
 
-    public static BitSet INIT_BITSET(final int ... b) {
-        final BitSet bitset = new BitSet();
-        for (final int aB : b) {
-            bitset.set(aB);
-        }
-        return bitset;
-    }
+    public static final TokenParser INSTANCE = new TokenParser();
 
     /** Double quote */
     public static final char DQUOTE = '\"';
@@ -59,211 +55,30 @@ public class TokenParser {
     /** Backward slash / escape character */
     public static final char ESCAPE = '\\';
 
-    public static boolean isWhitespace(final char ch) {
-        return ch == Chars.SP || ch == Chars.HT || ch == Chars.CR || ch == Chars.LF;
-    }
-
-    public static final TokenParser INSTANCE = new TokenParser();
-
-    /**
-     * 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
-     *  is not delimited by any character.
-     */
     public String parseToken(final CharSequence buf, final ParserCursor cursor, final BitSet delimiters) {
-        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)) {
-                break;
-            } else if (isWhitespace(current)) {
-                skipWhiteSpace(buf, cursor);
-                whitespace = true;
-            } else {
-                if (whitespace && dst.length() > 0) {
-                    dst.append(' ');
-                }
-                copyContent(buf, cursor, delimiters, dst);
-                whitespace = false;
-            }
-        }
-        return dst.toString();
+        return super.parseToken(buf, cursor, delimiters);
     }
 
-    /**
-     * Extracts from the sequence of chars a value which can be enclosed in quote marks and
-     * terminated with any of the given delimiters discarding semantically insignificant
-     * whitespace characters.
-     *
-     * @param buf buffer with the sequence of chars to be parsed
-     * @param cursor defines the bounds and current position of the buffer
-     * @param delimiters set of delimiting characters. Can be {@code null} if the value
-     *  is not delimited by any character.
-     */
     public String parseValue(final CharSequence buf, final ParserCursor cursor, final BitSet delimiters) {
-        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)) {
-                break;
-            } else if (isWhitespace(current)) {
-                skipWhiteSpace(buf, cursor);
-                whitespace = true;
-            } else if (current == DQUOTE) {
-                if (whitespace && dst.length() > 0) {
-                    dst.append(' ');
-                }
-                copyQuotedContent(buf, cursor, dst);
-                whitespace = false;
-            } else {
-                if (whitespace && dst.length() > 0) {
-                    dst.append(' ');
-                }
-                copyUnquotedContent(buf, cursor, delimiters, dst);
-                whitespace = false;
-            }
-        }
-        return dst.toString();
+        return super.parseValue(buf, cursor, delimiters);
     }
 
-    /**
-     * Skips semantically insignificant whitespace characters and moves the cursor to the closest
-     * non-whitespace character.
-     *
-     * @param buf buffer with the sequence of chars to be parsed
-     * @param cursor defines the bounds and current position of the buffer
-     */
     public void skipWhiteSpace(final CharSequence buf, final ParserCursor cursor) {
-        Args.notNull(buf, "Char sequence");
-        Args.notNull(cursor, "Parser cursor");
-        int pos = cursor.getPos();
-        final int indexFrom = cursor.getPos();
-        final int indexTo = cursor.getUpperBound();
-        for (int i = indexFrom; i < indexTo; i++) {
-            final char current = buf.charAt(i);
-            if (!isWhitespace(current)) {
-                break;
-            }
-            pos++;
-        }
-        cursor.updatePos(pos);
+        super.skipWhiteSpace(buf, cursor);
     }
 
-    /**
-     * Transfers content into the destination buffer until a whitespace character or any of
-     * the given delimiters is encountered.
-     *
-     * @param buf buffer with the sequence of chars to be parsed
-     * @param cursor defines the bounds and current position of the buffer
-     * @param delimiters set of delimiting characters. Can be {@code null} if the value
-     *  is delimited by a whitespace only.
-     * @param dst destination buffer
-     */
     public void copyContent(final CharSequence buf, final ParserCursor cursor, final BitSet delimiters,
-            final StringBuilder dst) {
-        Args.notNull(buf, "Char sequence");
-        Args.notNull(cursor, "Parser cursor");
-        Args.notNull(dst, "String builder");
-        int pos = cursor.getPos();
-        final int indexFrom = cursor.getPos();
-        final int indexTo = cursor.getUpperBound();
-        for (int i = indexFrom; i < indexTo; i++) {
-            final char current = buf.charAt(i);
-            if ((delimiters != null && delimiters.get(current)) || isWhitespace(current)) {
-                break;
-            }
-            pos++;
-            dst.append(current);
-        }
-        cursor.updatePos(pos);
+                            final StringBuilder dst) {
+        super.copyContent(buf, cursor, delimiters, 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
-     *  is delimited by a whitespace or a quote only.
-     * @param dst destination buffer
-     */
-    public void copyUnquotedContent(final CharSequence buf, final ParserCursor cursor,
-            final BitSet delimiters, final StringBuilder dst) {
-        Args.notNull(buf, "Char sequence");
-        Args.notNull(cursor, "Parser cursor");
-        Args.notNull(dst, "String builder");
-        int pos = cursor.getPos();
-        final int indexFrom = cursor.getPos();
-        final int indexTo = cursor.getUpperBound();
-        for (int i = indexFrom; i < indexTo; i++) {
-            final char current = buf.charAt(i);
-            if ((delimiters != null && delimiters.get(current))
-                    || isWhitespace(current) || current == DQUOTE) {
-                break;
-            }
-            pos++;
-            dst.append(current);
-        }
-        cursor.updatePos(pos);
+    public void copyUnquotedContent(final CharSequence buf, final ParserCursor cursor, final BitSet delimiters,
+                                    final StringBuilder dst) {
+        super.copyUnquotedContent(buf, cursor, delimiters, dst);
     }
 
-    /**
-     * Transfers content enclosed with quote marks into the destination buffer.
-     *
-     * @param buf buffer with the sequence of chars to be parsed
-     * @param cursor defines the bounds and current position of the buffer
-     * @param dst destination buffer
-     */
-    public void copyQuotedContent(final CharSequence buf, final ParserCursor cursor,
-            final StringBuilder dst) {
-        Args.notNull(buf, "Char sequence");
-        Args.notNull(cursor, "Parser cursor");
-        Args.notNull(dst, "String builder");
-        if (cursor.atEnd()) {
-            return;
-        }
-        int pos = cursor.getPos();
-        int indexFrom = cursor.getPos();
-        final int indexTo = cursor.getUpperBound();
-        char current = buf.charAt(pos);
-        if (current != DQUOTE) {
-            return;
-        }
-        pos++;
-        indexFrom++;
-        boolean escaped = false;
-        for (int i = indexFrom; i < indexTo; i++, pos++) {
-            current = buf.charAt(i);
-            if (escaped) {
-                if (current != DQUOTE && current != ESCAPE) {
-                    dst.append(ESCAPE);
-                }
-                dst.append(current);
-                escaped = false;
-            } else {
-                if (current == DQUOTE) {
-                    pos++;
-                    break;
-                }
-                if (current == ESCAPE) {
-                    escaped = true;
-                } else if (current != Chars.CR && current != Chars.LF) {
-                    dst.append(current);
-                }
-            }
-        }
-        cursor.updatePos(pos);
+    public void copyQuotedContent(final CharSequence buf, final ParserCursor cursor, final StringBuilder dst) {
+        super.copyQuotedContent(buf, cursor, dst);
     }
 
 }
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 c84cb8c..6836cdf 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
@@ -32,7 +32,7 @@ import java.util.List;
 
 import org.apache.hc.core5.http.ParseException;
 import org.apache.hc.core5.http.ProtocolVersion;
-import org.apache.hc.core5.http.message.ParserCursor;
+import org.apache.hc.core5.util.Tokenizer;
 
 /**
  * Supported {@code TLS} protocol versions.
@@ -74,7 +74,7 @@ public enum TLS {
         if (s == null) {
             return null;
         }
-        final ParserCursor cursor = new ParserCursor(0, s.length());
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
         return TlsVersionParser.INSTANCE.parse(s, cursor, null);
     }
 
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
index d207da8..6ad359c 100644
--- 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
@@ -31,22 +31,21 @@ import java.util.BitSet;
 
 import org.apache.hc.core5.http.ParseException;
 import org.apache.hc.core5.http.ProtocolVersion;
-import org.apache.hc.core5.http.message.ParserCursor;
-import org.apache.hc.core5.http.message.TokenParser;
+import org.apache.hc.core5.util.Tokenizer;
 
 final class TlsVersionParser {
 
     public final static TlsVersionParser INSTANCE = new TlsVersionParser();
 
-    private final TokenParser tokenParser;
+    private final Tokenizer tokenizer;
 
     TlsVersionParser() {
-        this.tokenParser = TokenParser.INSTANCE;
+        this.tokenizer = Tokenizer.INSTANCE;
     }
 
     ProtocolVersion parse(
             final CharSequence buffer,
-            final ParserCursor cursor,
+            final Tokenizer.Cursor cursor,
             final BitSet delimiters) throws ParseException {
         final int lowerBound = cursor.getLowerBound();
         final int upperBound = cursor.getUpperBound();
@@ -64,7 +63,7 @@ final class TlsVersionParser {
         if (cursor.atEnd()) {
             throw new ParseException("Invalid TLS version", buffer, lowerBound, upperBound, pos);
         }
-        final String s = this.tokenParser.parseToken(buffer, cursor, delimiters);
+        final String s = this.tokenizer.parseToken(buffer, cursor, delimiters);
         final int idx = s.indexOf('.');
         if (idx == -1) {
             final int major;
@@ -97,7 +96,7 @@ final class TlsVersionParser {
         if (s == null) {
             return null;
         }
-        final ParserCursor cursor = new ParserCursor(0, s.length());
+        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/net/URLEncodedUtils.java b/httpcore5/src/main/java/org/apache/hc/core5/net/URLEncodedUtils.java
index 93376be..c2131ea 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/net/URLEncodedUtils.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/URLEncodedUtils.java
@@ -40,9 +40,8 @@ import java.util.List;
 
 import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.http.message.BasicNameValuePair;
-import org.apache.hc.core5.http.message.ParserCursor;
-import org.apache.hc.core5.http.message.TokenParser;
 import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.Tokenizer;
 
 /**
  * A collection of utilities for encoding URLs.
@@ -110,12 +109,12 @@ public class URLEncodedUtils {
     public static List<NameValuePair> parse(
             final CharSequence s, final Charset charset, final char... separators) {
         Args.notNull(s, "Char sequence");
-        final TokenParser tokenParser = TokenParser.INSTANCE;
+        final Tokenizer tokenParser = Tokenizer.INSTANCE;
         final BitSet delimSet = new BitSet();
         for (final char separator: separators) {
             delimSet.set(separator);
         }
-        final ParserCursor cursor = new ParserCursor(0, s.length());
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
         final List<NameValuePair> list = new ArrayList<>();
         while (!cursor.atEnd()) {
             delimSet.set('=');
@@ -142,7 +141,7 @@ public class URLEncodedUtils {
     }
 
     static List<String> splitSegments(final CharSequence s, final BitSet separators) {
-        final ParserCursor cursor = new ParserCursor(0, s.length());
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
         // Skip leading separator
         if (cursor.atEnd()) {
             return Collections.emptyList();
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/TokenParser.java b/httpcore5/src/main/java/org/apache/hc/core5/util/Tokenizer.java
similarity index 71%
copy from httpcore5/src/main/java/org/apache/hc/core5/http/message/TokenParser.java
copy to httpcore5/src/main/java/org/apache/hc/core5/util/Tokenizer.java
index cdd6a21..ee66fd3 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/TokenParser.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/util/Tokenizer.java
@@ -25,25 +25,80 @@
  *
  */
 
-package org.apache.hc.core5.http.message;
+package org.apache.hc.core5.util;
 
 import java.util.BitSet;
 
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
-import org.apache.hc.core5.http.Chars;
-import org.apache.hc.core5.util.Args;
 
 /**
- * Low level parser for header field elements. The parsing routines of this class are designed
- * to produce near zero intermediate garbage and make no intermediate copies of input data.
+ * Tokenizer that can be used as a foundation for more complex parsing routines.
+ * Methods of this class are designed to produce near zero intermediate garbage
+ * and make no intermediate copies of input data.
  * <p>
  * This class is immutable and thread safe.
  *
- * @since 4.4
+ * @since 5.1
  */
 @Contract(threading = ThreadingBehavior.IMMUTABLE)
-public class TokenParser {
+public class Tokenizer {
+
+    public static class Cursor {
+
+        private final int lowerBound;
+        private final int upperBound;
+        private int pos;
+
+        public Cursor(final int lowerBound, final int upperBound) {
+            super();
+            Args.notNegative(lowerBound, "lowerBound");
+            Args.check(lowerBound <= upperBound, "lowerBound cannot be greater than upperBound");
+            this.lowerBound = lowerBound;
+            this.upperBound = upperBound;
+            this.pos = lowerBound;
+        }
+
+        public int getLowerBound() {
+            return this.lowerBound;
+        }
+
+        public int getUpperBound() {
+            return this.upperBound;
+        }
+
+        public int getPos() {
+            return this.pos;
+        }
+
+        public void updatePos(final int pos) {
+            if (pos < this.lowerBound) {
+                throw new IndexOutOfBoundsException("pos: "+pos+" < lowerBound: "+this.lowerBound);
+            }
+            if (pos > this.upperBound) {
+                throw new IndexOutOfBoundsException("pos: "+pos+" > upperBound: "+this.upperBound);
+            }
+            this.pos = pos;
+        }
+
+        public boolean atEnd() {
+            return this.pos >= this.upperBound;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder buffer = new StringBuilder();
+            buffer.append('[');
+            buffer.append(Integer.toString(this.lowerBound));
+            buffer.append('>');
+            buffer.append(Integer.toString(this.pos));
+            buffer.append('>');
+            buffer.append(Integer.toString(this.upperBound));
+            buffer.append(']');
+            return buffer.toString();
+        }
+
+    }
 
     public static BitSet INIT_BITSET(final int ... b) {
         final BitSet bitset = new BitSet();
@@ -59,11 +114,33 @@ public class TokenParser {
     /** Backward slash / escape character */
     public static final char ESCAPE = '\\';
 
+    public static final int CR = 13; // <US-ASCII CR, carriage return (13)>
+    public static final int LF = 10; // <US-ASCII LF, linefeed (10)>
+    public static final int SP = 32; // <US-ASCII SP, space (32)>
+    public static final int HT = 9;  // <US-ASCII HT, horizontal-tab (9)>
+
     public static boolean isWhitespace(final char ch) {
-        return ch == Chars.SP || ch == Chars.HT || ch == Chars.CR || ch == Chars.LF;
+        return ch == SP || ch == HT || ch == CR || ch == LF;
     }
 
-    public static final TokenParser INSTANCE = new TokenParser();
+    public static final Tokenizer INSTANCE = new Tokenizer();
+
+    /**
+     * Extracts from the sequence of chars a token terminated with any of the given delimiters
+     * or a whitespace characters.
+     *
+     * @param buf buffer with the sequence of chars to be parsed
+     * @param cursor defines the bounds and current position of the buffer
+     * @param delimiters set of delimiting characters. 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) {
+        Args.notNull(buf, "Char sequence");
+        Args.notNull(cursor, "Parser cursor");
+        final StringBuilder dst = new StringBuilder();
+        copyContent(buf, cursor, delimiters, dst);
+        return dst.toString();
+    }
 
     /**
      * Extracts from the sequence of chars a token terminated with any of the given delimiters
@@ -74,7 +151,7 @@ public class TokenParser {
      * @param delimiters set of delimiting characters. Can be {@code null} if the token
      *  is not delimited by any character.
      */
-    public String parseToken(final CharSequence buf, final ParserCursor cursor, final BitSet delimiters) {
+    public String parseToken(final CharSequence buf, final Cursor cursor, final BitSet delimiters) {
         Args.notNull(buf, "Char sequence");
         Args.notNull(cursor, "Parser cursor");
         final StringBuilder dst = new StringBuilder();
@@ -107,7 +184,7 @@ public class TokenParser {
      * @param delimiters set of delimiting characters. Can be {@code null} if the value
      *  is not delimited by any character.
      */
-    public String parseValue(final CharSequence buf, final ParserCursor cursor, final BitSet delimiters) {
+    public String parseValue(final CharSequence buf, final Cursor cursor, final BitSet delimiters) {
         Args.notNull(buf, "Char sequence");
         Args.notNull(cursor, "Parser cursor");
         final StringBuilder dst = new StringBuilder();
@@ -143,7 +220,7 @@ public class TokenParser {
      * @param buf buffer with the sequence of chars to be parsed
      * @param cursor defines the bounds and current position of the buffer
      */
-    public void skipWhiteSpace(final CharSequence buf, final ParserCursor cursor) {
+    public void skipWhiteSpace(final CharSequence buf, final Cursor cursor) {
         Args.notNull(buf, "Char sequence");
         Args.notNull(cursor, "Parser cursor");
         int pos = cursor.getPos();
@@ -169,8 +246,8 @@ public class TokenParser {
      *  is delimited by a whitespace only.
      * @param dst destination buffer
      */
-    public void copyContent(final CharSequence buf, final ParserCursor cursor, final BitSet delimiters,
-            final StringBuilder dst) {
+    public void copyContent(final CharSequence buf, final Cursor cursor, final BitSet delimiters,
+                            final StringBuilder dst) {
         Args.notNull(buf, "Char sequence");
         Args.notNull(cursor, "Parser cursor");
         Args.notNull(dst, "String builder");
@@ -198,7 +275,7 @@ public class TokenParser {
      *  is delimited by a whitespace or a quote only.
      * @param dst destination buffer
      */
-    public void copyUnquotedContent(final CharSequence buf, final ParserCursor cursor,
+    public void copyUnquotedContent(final CharSequence buf, final Cursor cursor,
             final BitSet delimiters, final StringBuilder dst) {
         Args.notNull(buf, "Char sequence");
         Args.notNull(cursor, "Parser cursor");
@@ -225,7 +302,7 @@ public class TokenParser {
      * @param cursor defines the bounds and current position of the buffer
      * @param dst destination buffer
      */
-    public void copyQuotedContent(final CharSequence buf, final ParserCursor cursor,
+    public void copyQuotedContent(final CharSequence buf, final Cursor cursor,
             final StringBuilder dst) {
         Args.notNull(buf, "Char sequence");
         Args.notNull(cursor, "Parser cursor");
@@ -258,7 +335,7 @@ public class TokenParser {
                 }
                 if (current == ESCAPE) {
                     escaped = true;
-                } else if (current != Chars.CR && current != Chars.LF) {
+                } else if (current != CR && current != LF) {
                     dst.append(current);
                 }
             }
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
index a6baccc..221f49d 100644
--- 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
@@ -29,8 +29,7 @@ package org.apache.hc.core5.http.ssl;
 
 import org.apache.hc.core5.http.ParseException;
 import org.apache.hc.core5.http.ProtocolVersion;
-import org.apache.hc.core5.http.message.ParserCursor;
-import org.apache.hc.core5.http.message.TokenParser;
+import org.apache.hc.core5.util.Tokenizer;
 import org.hamcrest.CoreMatchers;
 import org.junit.Assert;
 import org.junit.Before;
@@ -59,8 +58,8 @@ public class TestTlsVersionParser {
 
     @Test
     public void testParseBuffer() throws Exception {
-        final ParserCursor cursor = new ParserCursor(1, 13);
-        Assert.assertThat(impl.parse(" TLSv1.2,0000", cursor, TokenParser.INIT_BITSET(',')),
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(1, 13);
+        Assert.assertThat(impl.parse(" TLSv1.2,0000", cursor, Tokenizer.INIT_BITSET(',')),
                 CoreMatchers.equalTo(TLS.V_1_2.version));
         Assert.assertThat(cursor.getPos(), CoreMatchers.equalTo(8));
     }
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestTokenParser.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestTokenizer.java
similarity index 83%
rename from httpcore5/src/test/java/org/apache/hc/core5/http/message/TestTokenParser.java
rename to httpcore5/src/test/java/org/apache/hc/core5/util/TestTokenizer.java
index 1db53f1..e77cd28 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestTokenParser.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestTokenizer.java
@@ -25,20 +25,19 @@
  *
  */
 
-package org.apache.hc.core5.http.message;
+package org.apache.hc.core5.util;
 
-import org.apache.hc.core5.util.CharArrayBuffer;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-public class TestTokenParser {
+public class TestTokenizer {
 
-    private TokenParser parser;
+    private Tokenizer parser;
 
     @Before
     public void setUp() throws Exception {
-        parser = new TokenParser();
+        parser = new Tokenizer();
     }
 
     private static CharArrayBuffer createBuffer(final String value) {
@@ -54,7 +53,7 @@ public class TestTokenParser {
     public void testBasicTokenParsing() throws Exception {
         final String s = "   raw: \" some stuff \"";
         final CharArrayBuffer raw = createBuffer(s);
-        final ParserCursor cursor = new ParserCursor(0, s.length());
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
 
         parser.skipWhiteSpace(raw, cursor);
 
@@ -62,7 +61,7 @@ public class TestTokenParser {
         Assert.assertEquals(3, cursor.getPos());
 
         final StringBuilder strbuf1 = new StringBuilder();
-        parser.copyContent(raw, cursor, TokenParser.INIT_BITSET(':'), strbuf1);
+        parser.copyContent(raw, cursor, Tokenizer.INIT_BITSET(':'), strbuf1);
 
         Assert.assertFalse(cursor.atEnd());
         Assert.assertEquals(6, cursor.getPos());
@@ -92,7 +91,7 @@ public class TestTokenParser {
     public void testTokenParsingWithQuotedPairs() throws Exception {
         final String s = "raw: \"\\\"some\\stuff\\\\\"";
         final CharArrayBuffer raw = createBuffer(s);
-        final ParserCursor cursor = new ParserCursor(0, s.length());
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
 
         parser.skipWhiteSpace(raw, cursor);
 
@@ -100,7 +99,7 @@ public class TestTokenParser {
         Assert.assertEquals(0, cursor.getPos());
 
         final StringBuilder strbuf1 = new StringBuilder();
-        parser.copyContent(raw, cursor, TokenParser.INIT_BITSET(':'), strbuf1);
+        parser.copyContent(raw, cursor, Tokenizer.INIT_BITSET(':'), strbuf1);
 
         Assert.assertFalse(cursor.atEnd());
         Assert.assertEquals("raw", strbuf1.toString());
@@ -122,7 +121,7 @@ public class TestTokenParser {
     public void testTokenParsingIncompleteQuote() throws Exception {
         final String s = "\"stuff and more stuff  ";
         final CharArrayBuffer raw = createBuffer(s);
-        final ParserCursor cursor = new ParserCursor(0, s.length());
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
         final StringBuilder strbuf1 = new StringBuilder();
         parser.copyQuotedContent(raw, cursor, strbuf1);
         Assert.assertEquals("stuff and more stuff  ", strbuf1.toString());
@@ -132,8 +131,8 @@ public class TestTokenParser {
     public void testTokenParsingTokensWithUnquotedBlanks() throws Exception {
         final String s = "  stuff and   \tsome\tmore  stuff  ;";
         final CharArrayBuffer raw = createBuffer(s);
-        final ParserCursor cursor = new ParserCursor(0, s.length());
-        final String result = parser.parseToken(raw, cursor, TokenParser.INIT_BITSET(';'));
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
+        final String result = parser.parseToken(raw, cursor, Tokenizer.INIT_BITSET(';'));
         Assert.assertEquals("stuff and some more stuff", result);
     }
 
@@ -141,8 +140,8 @@ public class TestTokenParser {
     public void testTokenParsingMixedValuesAndQuotedValues() throws Exception {
         final String s = "  stuff and    \" some more \"   \"stuff  ;";
         final CharArrayBuffer raw = createBuffer(s);
-        final ParserCursor cursor = new ParserCursor(0, s.length());
-        final String result = parser.parseValue(raw, cursor, TokenParser.INIT_BITSET(';'));
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
+        final String result = parser.parseValue(raw, cursor, Tokenizer.INIT_BITSET(';'));
         Assert.assertEquals("stuff and  some more  stuff  ;", result);
     }
 
@@ -150,8 +149,8 @@ public class TestTokenParser {
     public void testTokenParsingMixedValuesAndQuotedValues2() throws Exception {
         final String s = "stuff\"more\"stuff;";
         final CharArrayBuffer raw = createBuffer(s);
-        final ParserCursor cursor = new ParserCursor(0, s.length());
-        final String result = parser.parseValue(raw, cursor, TokenParser.INIT_BITSET(';'));
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
+        final String result = parser.parseValue(raw, cursor, Tokenizer.INIT_BITSET(';'));
         Assert.assertEquals("stuffmorestuff", result);
     }
 
@@ -159,8 +158,8 @@ public class TestTokenParser {
     public void testTokenParsingEscapedQuotes() throws Exception {
         final String s = "stuff\"\\\"more\\\"\"stuff;";
         final CharArrayBuffer raw = createBuffer(s);
-        final ParserCursor cursor = new ParserCursor(0, s.length());
-        final String result = parser.parseValue(raw, cursor, TokenParser.INIT_BITSET(';'));
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
+        final String result = parser.parseValue(raw, cursor, Tokenizer.INIT_BITSET(';'));
         Assert.assertEquals("stuff\"more\"stuff", result);
     }
 
@@ -168,8 +167,8 @@ public class TestTokenParser {
     public void testTokenParsingEscapedDelimiter() throws Exception {
         final String s = "stuff\"\\\"more\\\";\"stuff;";
         final CharArrayBuffer raw = createBuffer(s);
-        final ParserCursor cursor = new ParserCursor(0, s.length());
-        final String result = parser.parseValue(raw, cursor, TokenParser.INIT_BITSET(';'));
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
+        final String result = parser.parseValue(raw, cursor, Tokenizer.INIT_BITSET(';'));
         Assert.assertEquals("stuff\"more\";stuff", result);
     }
 
@@ -177,8 +176,8 @@ public class TestTokenParser {
     public void testTokenParsingEscapedSlash() throws Exception {
         final String s = "stuff\"\\\"more\\\";\\\\\"stuff;";
         final CharArrayBuffer raw = createBuffer(s);
-        final ParserCursor cursor = new ParserCursor(0, s.length());
-        final String result = parser.parseValue(raw, cursor, TokenParser.INIT_BITSET(';'));
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
+        final String result = parser.parseValue(raw, cursor, Tokenizer.INIT_BITSET(';'));
         Assert.assertEquals("stuff\"more\";\\stuff", result);
     }
 
@@ -186,8 +185,8 @@ public class TestTokenParser {
     public void testTokenParsingSlashOutsideQuotes() throws Exception {
         final String s = "stuff\\; more stuff;";
         final CharArrayBuffer raw = createBuffer(s);
-        final ParserCursor cursor = new ParserCursor(0, s.length());
-        final String result = parser.parseValue(raw, cursor, TokenParser.INIT_BITSET(';'));
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
+        final String result = parser.parseValue(raw, cursor, Tokenizer.INIT_BITSET(';'));
         Assert.assertEquals("stuff\\", result);
     }
 }


[httpcomponents-core] 13/18: HTTPCORE-639: Add a configurable ResponseOutOfOrder strategy for DefaultBHttpClientConnection

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit bf7759d3794a35979a3a5d07e823cde771b19790
Author: Carter Kozak <c4...@gmail.com>
AuthorDate: Mon Jul 27 10:17:45 2020 -0400

    HTTPCORE-639: Add a configurable ResponseOutOfOrder strategy for DefaultBHttpClientConnection
    
    This adds a configurable ResponseOutOfOrderStrategy in place of the
    previous always-enabled behavior, and uses the no-op
    NoResponseOutOfOrderStrategy implementation by default.
    
    The previous behavior can be used by selecting the
    MonitoringResponseOutOfOrderStrategy, which has been updated
    to support more flexible behavior. Note that this strategy
    results in a 1 ms pause for every chunk transferred, limiting
    upload speed using the default 8 KiB chunk size to at most
    8 MiB/second.
    
    The original discussion can be found on the mailing list:
    https://www.mail-archive.com/httpclient-users@hc.apache.org/msg09911.html
    
    This closes #206
---
 ...gResponseOutOfOrderStrategyIntegrationTest.java | 215 +++++++++++++++++++++
 .../http/impl/io/DefaultBHttpClientConnection.java | 105 +++++++++-
 .../io/DefaultBHttpClientConnectionFactory.java    |  32 ++-
 .../io/MonitoringResponseOutOfOrderStrategy.java   | 111 +++++++++++
 .../http/impl/io/NoResponseOutOfOrderStrategy.java |  61 ++++++
 .../http/impl/io/ResponseOutOfOrderException.java  |  41 ++++
 .../core5/http/io/ResponseOutOfOrderStrategy.java  |  66 +++++++
 .../java/org/apache/hc/core5/util/Timeout.java     |   5 +
 .../TestMonitoringResponseOutOfOrderStrategy.java  | 133 +++++++++++++
 9 files changed, 764 insertions(+), 5 deletions(-)

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
new file mode 100644
index 0000000..fad4491
--- /dev/null
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/MonitoringResponseOutOfOrderStrategyIntegrationTest.java
@@ -0,0 +1,215 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.testing.classic;
+
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.Method;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.impl.bootstrap.HttpRequester;
+import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap;
+import org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnectionFactory;
+import org.apache.hc.core5.http.impl.io.MonitoringResponseOutOfOrderStrategy;
+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.HttpContext;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.testing.SSLTestContexts;
+import org.apache.hc.core5.util.Timeout;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public class MonitoringResponseOutOfOrderStrategyIntegrationTest {
+
+    // Use a 16k buffer for consistent results across systems
+    private static final int BUFFER_SIZE = 16 * 1024;
+    private static final Timeout TIMEOUT = Timeout.ofSeconds(3);
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection<Object[]> protocols() {
+        return Arrays.asList(new Object[][]{
+                { URIScheme.HTTP },
+                { URIScheme.HTTPS }
+        });
+    }
+
+    private final URIScheme scheme;
+    private ClassicTestServer server;
+    private HttpRequester requester;
+
+    public MonitoringResponseOutOfOrderStrategyIntegrationTest(final URIScheme scheme) {
+        this.scheme = scheme;
+    }
+
+    @Rule
+    public ExternalResource serverResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            server = new ClassicTestServer(
+                    scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null,
+                    SocketConfig.custom()
+                            .setSoTimeout(TIMEOUT)
+                            .setSndBufSize(BUFFER_SIZE)
+                            .setRcvBufSize(BUFFER_SIZE)
+                            .setSoKeepAlive(false)
+                            .build());
+        }
+
+        @Override
+        protected void after() {
+            if (server != null) {
+                try {
+                    server.shutdown(CloseMode.IMMEDIATE);
+                    server = null;
+                } catch (final Exception ignore) {
+                }
+            }
+        }
+
+    };
+
+    @Rule
+    public ExternalResource requesterResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            requester = RequesterBootstrap.bootstrap()
+                    .setSslContext(scheme == URIScheme.HTTPS  ? SSLTestContexts.createClientSSLContext() : null)
+                    .setSocketConfig(SocketConfig.custom()
+                            .setSoTimeout(TIMEOUT)
+                            .setRcvBufSize(BUFFER_SIZE)
+                            .setSndBufSize(BUFFER_SIZE)
+                            .setSoKeepAlive(false)
+                            .build())
+                    .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
+                    .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
+                    .setConnectionFactory(DefaultBHttpClientConnectionFactory.builder()
+                            .responseOutOfOrderStrategy(MonitoringResponseOutOfOrderStrategy.INSTANCE)
+                            .build())
+                    .create();
+        }
+
+        @Override
+        protected void after() {
+            if (requester != null) {
+                try {
+                    requester.close(CloseMode.IMMEDIATE);
+                    requester = null;
+                } catch (final Exception ignore) {
+                }
+            }
+        }
+
+    };
+
+    @Test(timeout = 5000) // Failures may hang
+    public void testResponseOutOfOrderWithDefaultStrategy() throws Exception {
+        this.server.registerHandler("*", new HttpRequestHandler() {
+
+            @Override
+            public void handle(
+                    final ClassicHttpRequest request,
+                    final ClassicHttpResponse response,
+                    final HttpContext context) throws IOException {
+                response.setCode(400);
+                response.setEntity(new AllOnesHttpEntity(200000));
+            }
+
+        });
+
+        this.server.start(null, null, null);
+
+        final HttpCoreContext context = HttpCoreContext.create();
+        final HttpHost host = new HttpHost(scheme.id, "localhost", this.server.getPort());
+
+        final ClassicHttpRequest post = new BasicClassicHttpRequest(Method.POST, "/");
+        post.setEntity(new AllOnesHttpEntity(200000));
+
+        try (final ClassicHttpResponse response = requester.execute(host, post, TIMEOUT, context)) {
+            Assert.assertEquals(400, response.getCode());
+            EntityUtils.consumeQuietly(response.getEntity());
+        }
+    }
+
+    private static final class AllOnesHttpEntity extends AbstractHttpEntity {
+        private long remaining;
+
+        protected AllOnesHttpEntity(final long length) {
+            super(ContentType.APPLICATION_OCTET_STREAM, null, true);
+            this.remaining = length;
+        }
+
+        @Override
+        public InputStream getContent() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void writeTo(final OutputStream outStream) throws IOException {
+            final byte[] buf = new byte[1024];
+            while (remaining > 0) {
+                final int writeLength = (int) Math.min(remaining, buf.length);
+                outStream.write(buf, 0, writeLength);
+                outStream.flush();
+                remaining -= writeLength;
+            }
+        }
+
+        @Override
+        public boolean isStreaming() {
+            return true;
+        }
+
+        @Override
+        public void close() {
+        }
+
+        @Override
+        public long getContentLength() {
+            return -1L;
+        }
+    }
+}
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 dd5c1e3..d905bab 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
@@ -28,6 +28,7 @@
 package org.apache.hc.core5.http.impl.io;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
 import java.nio.charset.CharsetDecoder;
@@ -51,6 +52,7 @@ import org.apache.hc.core5.http.io.HttpMessageParser;
 import org.apache.hc.core5.http.io.HttpMessageParserFactory;
 import org.apache.hc.core5.http.io.HttpMessageWriter;
 import org.apache.hc.core5.http.io.HttpMessageWriterFactory;
+import org.apache.hc.core5.http.io.ResponseOutOfOrderStrategy;
 import org.apache.hc.core5.util.Args;
 
 /**
@@ -65,6 +67,7 @@ public class DefaultBHttpClientConnection extends BHttpConnectionBase
     private final HttpMessageWriter<ClassicHttpRequest> requestWriter;
     private final ContentLengthStrategy incomingContentStrategy;
     private final ContentLengthStrategy outgoingContentStrategy;
+    private final ResponseOutOfOrderStrategy responseOutOfOrderStrategy;
     private volatile boolean consistent;
 
     /**
@@ -80,6 +83,8 @@ public class DefaultBHttpClientConnection extends BHttpConnectionBase
      *   {@link DefaultContentLengthStrategy#INSTANCE} will be used.
      * @param outgoingContentStrategy outgoing content length strategy. If {@code null}
      *   {@link DefaultContentLengthStrategy#INSTANCE} will be used.
+     * @param responseOutOfOrderStrategy response out of order strategy. If {@code null}
+     *   {@link NoResponseOutOfOrderStrategy#INSTANCE} will be used.
      * @param requestWriterFactory request writer factory. If {@code null}
      *   {@link DefaultHttpRequestWriterFactory#INSTANCE} will be used.
      * @param responseParserFactory response parser factory. If {@code null}
@@ -91,6 +96,7 @@ public class DefaultBHttpClientConnection extends BHttpConnectionBase
             final CharsetEncoder charEncoder,
             final ContentLengthStrategy incomingContentStrategy,
             final ContentLengthStrategy outgoingContentStrategy,
+            final ResponseOutOfOrderStrategy responseOutOfOrderStrategy,
             final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory,
             final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) {
         super(http1Config, charDecoder, charEncoder);
@@ -99,12 +105,51 @@ public class DefaultBHttpClientConnection extends BHttpConnectionBase
         this.responseParser = (responseParserFactory != null ? responseParserFactory :
             DefaultHttpResponseParserFactory.INSTANCE).create(http1Config);
         this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy :
-                DefaultContentLengthStrategy.INSTANCE;
+            DefaultContentLengthStrategy.INSTANCE;
         this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy :
-                DefaultContentLengthStrategy.INSTANCE;
+            DefaultContentLengthStrategy.INSTANCE;
+        this.responseOutOfOrderStrategy = responseOutOfOrderStrategy != null ? responseOutOfOrderStrategy :
+            NoResponseOutOfOrderStrategy.INSTANCE;
         this.consistent = true;
     }
 
+    /**
+     * Creates new instance of DefaultBHttpClientConnection.
+     *
+     * @param http1Config Message http1Config. If {@code null}
+     *   {@link Http1Config#DEFAULT} will be used.
+     * @param charDecoder decoder to be used for decoding HTTP protocol elements.
+     *   If {@code null} simple type cast will be used for byte to char conversion.
+     * @param charEncoder encoder to be used for encoding HTTP protocol elements.
+     *   If {@code null} simple type cast will be used for char to byte conversion.
+     * @param incomingContentStrategy incoming content length strategy. If {@code null}
+     *   {@link DefaultContentLengthStrategy#INSTANCE} will be used.
+     * @param outgoingContentStrategy outgoing content length strategy. If {@code null}
+     *   {@link DefaultContentLengthStrategy#INSTANCE} will be used.
+     * @param requestWriterFactory request writer factory. If {@code null}
+     *   {@link DefaultHttpRequestWriterFactory#INSTANCE} will be used.
+     * @param responseParserFactory response parser factory. If {@code null}
+     *   {@link DefaultHttpResponseParserFactory#INSTANCE} will be used.
+     */
+    public DefaultBHttpClientConnection(
+            final Http1Config http1Config,
+            final CharsetDecoder charDecoder,
+            final CharsetEncoder charEncoder,
+            final ContentLengthStrategy incomingContentStrategy,
+            final ContentLengthStrategy outgoingContentStrategy,
+            final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory,
+            final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) {
+        this(
+                http1Config,
+                charDecoder,
+                charEncoder,
+                incomingContentStrategy,
+                outgoingContentStrategy,
+                null,
+                requestWriterFactory,
+                responseParserFactory);
+    }
+
     public DefaultBHttpClientConnection(
             final Http1Config http1Config,
             final CharsetDecoder charDecoder,
@@ -149,8 +194,62 @@ public class DefaultBHttpClientConnection extends BHttpConnectionBase
         if (len == ContentLengthStrategy.UNDEFINED) {
             throw new LengthRequiredException();
         }
-        try (final OutputStream outStream = createContentOutputStream(len, this.outbuffer, socketHolder.getOutputStream(), entity.getTrailers())) {
+        try (final OutputStream outStream = createContentOutputStream(
+                len, this.outbuffer, new OutputStream() {
+
+                    final OutputStream socketOutputStream = socketHolder.getOutputStream();
+                    final InputStream socketInputStream = socketHolder.getInputStream();
+
+                    long totalBytes = 0;
+
+                    void checkForEarlyResponse(final long totalBytesSent, final int nextWriteSize) throws IOException {
+                        if (responseOutOfOrderStrategy.isEarlyResponseDetected(
+                                request,
+                                DefaultBHttpClientConnection.this,
+                                socketInputStream,
+                                totalBytesSent,
+                                nextWriteSize)) {
+                            throw new ResponseOutOfOrderException();
+                        }
+                    }
+
+                    @Override
+                    public void write(final byte[] b) throws IOException {
+                        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);
+                        totalBytes += len;
+                        socketOutputStream.write(b, off, len);
+                    }
+
+                    @Override
+                    public void write(final int b) throws IOException {
+                        checkForEarlyResponse(totalBytes, 1);
+                        totalBytes++;
+                        socketOutputStream.write(b);
+                    }
+
+                    @Override
+                    public void flush() throws IOException {
+                        socketOutputStream.flush();
+                    }
+
+                    @Override
+                    public void close() throws IOException {
+                        socketOutputStream.close();
+                    }
+
+                }, entity.getTrailers())) {
             entity.writeTo(outStream);
+        } catch (final ResponseOutOfOrderException ex) {
+            if (len > 0) {
+                this.consistent = false;
+            }
         }
     }
 
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 37c2e48..c2becd2 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
@@ -41,6 +41,7 @@ import org.apache.hc.core5.http.impl.CharCodingSupport;
 import org.apache.hc.core5.http.io.HttpConnectionFactory;
 import org.apache.hc.core5.http.io.HttpMessageParserFactory;
 import org.apache.hc.core5.http.io.HttpMessageWriterFactory;
+import org.apache.hc.core5.http.io.ResponseOutOfOrderStrategy;
 
 /**
  * Default factory for {@link org.apache.hc.core5.http.io.HttpClientConnection}s.
@@ -55,21 +56,23 @@ public class DefaultBHttpClientConnectionFactory
     private final CharCodingConfig charCodingConfig;
     private final ContentLengthStrategy incomingContentStrategy;
     private final ContentLengthStrategy outgoingContentStrategy;
+    private final ResponseOutOfOrderStrategy responseOutOfOrderStrategy;
     private final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory;
     private final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory;
 
-    public DefaultBHttpClientConnectionFactory(
+    private DefaultBHttpClientConnectionFactory(
             final Http1Config http1Config,
             final CharCodingConfig charCodingConfig,
             final ContentLengthStrategy incomingContentStrategy,
             final ContentLengthStrategy outgoingContentStrategy,
+            final ResponseOutOfOrderStrategy responseOutOfOrderStrategy,
             final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory,
             final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) {
-        super();
         this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT;
         this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT;
         this.incomingContentStrategy = incomingContentStrategy;
         this.outgoingContentStrategy = outgoingContentStrategy;
+        this.responseOutOfOrderStrategy = responseOutOfOrderStrategy;
         this.requestWriterFactory = requestWriterFactory;
         this.responseParserFactory = responseParserFactory;
     }
@@ -77,6 +80,23 @@ public class DefaultBHttpClientConnectionFactory
     public DefaultBHttpClientConnectionFactory(
             final Http1Config http1Config,
             final CharCodingConfig charCodingConfig,
+            final ContentLengthStrategy incomingContentStrategy,
+            final ContentLengthStrategy outgoingContentStrategy,
+            final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory,
+            final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) {
+        this(
+                http1Config,
+                charCodingConfig,
+                incomingContentStrategy,
+                outgoingContentStrategy,
+                null,
+                requestWriterFactory,
+                responseParserFactory);
+    }
+
+    public DefaultBHttpClientConnectionFactory(
+            final Http1Config http1Config,
+            final CharCodingConfig charCodingConfig,
             final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory,
             final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) {
         this(http1Config, charCodingConfig, null, null, requestWriterFactory, responseParserFactory);
@@ -100,6 +120,7 @@ public class DefaultBHttpClientConnectionFactory
                 CharCodingSupport.createEncoder(this.charCodingConfig),
                 this.incomingContentStrategy,
                 this.outgoingContentStrategy,
+                this.responseOutOfOrderStrategy,
                 this.requestWriterFactory,
                 this.responseParserFactory);
         conn.bind(socket);
@@ -125,6 +146,7 @@ public class DefaultBHttpClientConnectionFactory
         private CharCodingConfig charCodingConfig;
         private ContentLengthStrategy incomingContentLengthStrategy;
         private ContentLengthStrategy outgoingContentLengthStrategy;
+        private ResponseOutOfOrderStrategy responseOutOfOrderStrategy;
         private HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory;
         private HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory;
 
@@ -150,6 +172,11 @@ public class DefaultBHttpClientConnectionFactory
             return this;
         }
 
+        public Builder responseOutOfOrderStrategy(final ResponseOutOfOrderStrategy responseOutOfOrderStrategy) {
+            this.responseOutOfOrderStrategy = responseOutOfOrderStrategy;
+            return this;
+        }
+
         public Builder requestWriterFactory(
                 final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory) {
             this.requestWriterFactory = requestWriterFactory;
@@ -168,6 +195,7 @@ public class DefaultBHttpClientConnectionFactory
                     charCodingConfig,
                     incomingContentLengthStrategy,
                     outgoingContentLengthStrategy,
+                    responseOutOfOrderStrategy,
                     requestWriterFactory,
                     responseParserFactory);
         }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/MonitoringResponseOutOfOrderStrategy.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/MonitoringResponseOutOfOrderStrategy.java
new file mode 100644
index 0000000..303e61b
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/MonitoringResponseOutOfOrderStrategy.java
@@ -0,0 +1,111 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.http.impl.io;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.io.HttpClientConnection;
+import org.apache.hc.core5.http.io.ResponseOutOfOrderStrategy;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.Timeout;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A {@link ResponseOutOfOrderStrategy} implementation which checks for premature responses every {@link #chunkSize}
+ * bytes. An 8 KiB chunk size is used by default based on testing using values between 4 KiB and 128 KiB. This is
+ * optimized for correctness and results in a maximum upload speed of 8 MiB/s until {@link #maxChunksToCheck} is
+ * reached.
+ *
+ * @since 5.1
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public final class MonitoringResponseOutOfOrderStrategy implements ResponseOutOfOrderStrategy {
+
+    private static final int DEFAULT_CHUNK_SIZE = 8 * 1024;
+
+    public static final MonitoringResponseOutOfOrderStrategy INSTANCE = new MonitoringResponseOutOfOrderStrategy();
+
+    private final long chunkSize;
+    private final long maxChunksToCheck;
+
+    /**
+     * Instantiates a default {@link MonitoringResponseOutOfOrderStrategy}. {@link #INSTANCE} may be used instead.
+     */
+    public MonitoringResponseOutOfOrderStrategy() {
+        this(DEFAULT_CHUNK_SIZE);
+    }
+
+    /**
+     * Instantiates a {@link MonitoringResponseOutOfOrderStrategy} with unlimited {@link #maxChunksToCheck}.
+     *
+     * @param chunkSize The chunk size after which a response check is executed.
+     */
+    public MonitoringResponseOutOfOrderStrategy(final long chunkSize) {
+        this(chunkSize, Long.MAX_VALUE);
+    }
+
+    /**
+     * Instantiates a {@link MonitoringResponseOutOfOrderStrategy}.
+     *
+     * @param chunkSize The chunk size after which a response check is executed.
+     * @param maxChunksToCheck The maximum number of chunks to check, allowing expensive checks to be avoided
+     *                         after a sufficient portion of the request entity has been transferred.
+     */
+    public MonitoringResponseOutOfOrderStrategy(final long chunkSize, final long maxChunksToCheck) {
+        this.chunkSize = Args.positive(chunkSize, "chunkSize");
+        this.maxChunksToCheck = Args.positive(maxChunksToCheck, "maxChunksToCheck");
+    }
+
+    @Override
+    public boolean isEarlyResponseDetected(
+            final ClassicHttpRequest request,
+            final HttpClientConnection connection,
+            final InputStream inputStream,
+            final long totalBytesSent,
+            final long nextWriteSize) throws IOException {
+        if (nextWriteStartsNewChunk(totalBytesSent, nextWriteSize)) {
+            final boolean ssl = connection.getSSLSession() != null;
+            return ssl ? connection.isDataAvailable(Timeout.ONE_MILLISECOND) : (inputStream.available() > 0);
+        }
+        return false;
+    }
+
+    private boolean nextWriteStartsNewChunk(final long totalBytesSent, final long nextWriteSize) {
+        final long currentChunkIndex = Math.min(totalBytesSent / chunkSize, maxChunksToCheck);
+        final long newChunkIndex = Math.min((totalBytesSent + nextWriteSize) / chunkSize, maxChunksToCheck);
+        return currentChunkIndex < newChunkIndex;
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultResponseOutOfOrderStrategy{chunkSize=" + chunkSize + ", maxChunksToCheck=" + maxChunksToCheck + '}';
+    }
+}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/NoResponseOutOfOrderStrategy.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/NoResponseOutOfOrderStrategy.java
new file mode 100644
index 0000000..94606e2
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/NoResponseOutOfOrderStrategy.java
@@ -0,0 +1,61 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.http.impl.io;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.io.HttpClientConnection;
+import org.apache.hc.core5.http.io.ResponseOutOfOrderStrategy;
+
+import java.io.InputStream;
+
+/**
+ * An implementation of {@link ResponseOutOfOrderStrategy} which does not check for early responses.
+ *
+ * Early response detection requires 1ms blocking reads and incurs a hefty performance cost for
+ * large uploads.
+ *
+ * @see MonitoringResponseOutOfOrderStrategy
+ * @since 5.1
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public final class NoResponseOutOfOrderStrategy implements ResponseOutOfOrderStrategy {
+
+    public static final NoResponseOutOfOrderStrategy INSTANCE = new NoResponseOutOfOrderStrategy();
+
+    @Override
+    public boolean isEarlyResponseDetected(
+            final ClassicHttpRequest request,
+            final HttpClientConnection connection,
+            final InputStream inputStream,
+            final long totalBytesSent,
+            final long nextWriteSize) {
+        return false;
+    }
+}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ResponseOutOfOrderException.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ResponseOutOfOrderException.java
new file mode 100644
index 0000000..5744342
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ResponseOutOfOrderException.java
@@ -0,0 +1,41 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.http.impl.io;
+
+import java.io.IOException;
+
+/**
+ * Signals an early (out of order) response.
+ */
+class ResponseOutOfOrderException extends IOException {
+
+    public ResponseOutOfOrderException() {
+        super();
+    }
+
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..ff03f68
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/ResponseOutOfOrderStrategy.java
@@ -0,0 +1,66 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.http.io;
+
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 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}
+ * may be thrown while writing the request entity after a timeout is reached on either the client or server.
+ *
+ * @since 5.1
+ */
+@Internal
+public interface ResponseOutOfOrderStrategy {
+
+    /**
+     * Called before each write to the 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.
+     *
+     * @param request The current request.
+     * @param connection The connection used to send the current request.
+     * @param inputStream The response stream, this may be used to check for an early response.
+     * @param totalBytesSent Number of bytes that have already been sent.
+     * @param nextWriteSize The size of a socket write operation that will follow this check.
+     * @return True if an early response was detected, otherwise false.
+     * @throws IOException in case of a network failure while checking for an early response.
+     */
+    boolean isEarlyResponseDetected(
+            ClassicHttpRequest request,
+            HttpClientConnection connection,
+            InputStream inputStream,
+            long totalBytesSent,
+            long nextWriteSize) throws IOException;
+}
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 f6a835a..77619f6 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
@@ -47,6 +47,11 @@ public class Timeout extends TimeValue {
     public static final Timeout ZERO_MILLISECONDS = Timeout.of(0, TimeUnit.MILLISECONDS);
 
     /**
+     * A one milliseconds {@link Timeout}.
+     */
+    public static final Timeout ONE_MILLISECOND = Timeout.of(1, TimeUnit.MILLISECONDS);
+
+    /**
      * A disabled timeout represented as 0 {@code MILLISECONDS}.
      */
     public static final Timeout DISABLED = ZERO_MILLISECONDS;
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
new file mode 100644
index 0000000..4c8966e
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMonitoringResponseOutOfOrderStrategy.java
@@ -0,0 +1,133 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.http.impl.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.net.ssl.SSLSession;
+
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.io.HttpClientConnection;
+import org.apache.hc.core5.http.io.ResponseOutOfOrderStrategy;
+import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
+import org.apache.hc.core5.util.Timeout;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+
+public class TestMonitoringResponseOutOfOrderStrategy {
+
+    private static final ClassicHttpRequest REQUEST = new BasicClassicHttpRequest("POST", "/path");
+
+    @Test
+    public void testFirstByteIsNotCheckedSsl() throws IOException {
+        final boolean earlyResponse = MonitoringResponseOutOfOrderStrategy.INSTANCE.isEarlyResponseDetected(
+                REQUEST,
+                connection(true, true),
+                // SSLSocket streams report zero bytes available
+                socketInputStream(0),
+                0,
+                1);
+        Assert.assertFalse(earlyResponse);
+    }
+
+    @Test
+    public void testFirstByteIsNotCheckedPlain() throws IOException {
+        final boolean earlyResponse = MonitoringResponseOutOfOrderStrategy.INSTANCE.isEarlyResponseDetected(
+                REQUEST,
+                connection(true, false),
+                socketInputStream(1),
+                0,
+                1);
+        Assert.assertFalse(earlyResponse);
+    }
+
+    @Test
+    public void testWritesWithinChunkAreNotChecked() throws IOException {
+        final boolean earlyResponse = MonitoringResponseOutOfOrderStrategy.INSTANCE.isEarlyResponseDetected(
+                REQUEST,
+                connection(true, true),
+                socketInputStream(0),
+                1,
+                8190);
+        Assert.assertFalse(
+                "There is data available, but checks shouldn't occur until just prior to the 8192nd byte",
+                earlyResponse);
+    }
+
+    @Test
+    public void testWritesAcrossChunksAreChecked() throws IOException {
+        final boolean earlyResponse = MonitoringResponseOutOfOrderStrategy.INSTANCE.isEarlyResponseDetected(
+                REQUEST,
+                connection(true, true),
+                socketInputStream(0),
+                8191,
+                1);
+        Assert.assertTrue(earlyResponse);
+    }
+
+    @Test
+    public void testMaximumChunks() throws IOException {
+        final ResponseOutOfOrderStrategy strategy = new MonitoringResponseOutOfOrderStrategy(1, 2);
+        Assert.assertTrue(strategy.isEarlyResponseDetected(
+                REQUEST,
+                connection(true, true),
+                socketInputStream(0),
+                0,
+                1));
+        Assert.assertTrue(strategy.isEarlyResponseDetected(
+                REQUEST,
+                connection(true, true),
+                socketInputStream(0),
+                1,
+                2));
+        Assert.assertFalse(strategy.isEarlyResponseDetected(
+                REQUEST,
+                connection(true, true),
+                socketInputStream(0),
+                2,
+                3));
+    }
+
+    private static InputStream socketInputStream(final int available) throws IOException {
+        final InputStream stream = Mockito.mock(InputStream.class);
+        Mockito.when(stream.available()).thenReturn(available);
+        return stream;
+    }
+
+    private static HttpClientConnection connection(final boolean dataAvailable, final boolean ssl) throws IOException {
+        final HttpClientConnection connection = Mockito.mock(HttpClientConnection.class);
+        Mockito.when(connection.isDataAvailable(ArgumentMatchers.any(Timeout.class))).thenReturn(dataAvailable);
+        if (ssl) {
+            Mockito.when(connection.getSSLSession()).thenReturn(Mockito.mock(SSLSession.class));
+        }
+        return connection;
+    }
+}


[httpcomponents-core] 08/18: Better parse and format methods for URIAuthority

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit fd335d217c84cc5595091dc678ff13d04718c28e
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Jul 25 12:00:15 2020 +0200

    Better parse and format methods for URIAuthority
---
 .../java/org/apache/hc/core5/net/URIAuthority.java | 135 +++++++++++++++------
 .../org/apache/hc/core5/net/TestURIAuthority.java  |  88 ++++++++++++--
 2 files changed, 181 insertions(+), 42 deletions(-)

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 01feb0f..1cae817 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
@@ -29,6 +29,7 @@ package org.apache.hc.core5.net;
 
 import java.io.Serializable;
 import java.net.URISyntaxException;
+import java.util.BitSet;
 import java.util.Locale;
 
 import org.apache.hc.core5.annotation.Contract;
@@ -36,6 +37,7 @@ import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.util.Args;
 import org.apache.hc.core5.util.LangUtils;
 import org.apache.hc.core5.util.TextUtils;
+import org.apache.hc.core5.util.Tokenizer;
 
 /**
  * Represents authority component of request {@link java.net.URI}.
@@ -50,6 +52,97 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
     private final String hostname;
     private final int port;
 
+    private static final BitSet HOST_SEPARATORS = new BitSet(256);
+    private static final BitSet PORT_SEPARATORS = new BitSet(256);
+    private static final BitSet TERMINATORS = new BitSet(256);
+
+    static {
+        TERMINATORS.set('/');
+        TERMINATORS.set('#');
+        TERMINATORS.set('?');
+        HOST_SEPARATORS.or(TERMINATORS);
+        HOST_SEPARATORS.set('@');
+        HOST_SEPARATORS.set(':');
+        PORT_SEPARATORS.or(TERMINATORS);
+        PORT_SEPARATORS.set(':');
+    }
+
+    static URISyntaxException createException(
+            final CharSequence input, final Tokenizer.Cursor cursor, final String reason) {
+        return new URISyntaxException(
+                input.subSequence(cursor.getLowerBound(), cursor.getUpperBound()).toString(),
+                reason,
+                cursor.getPos());
+    }
+
+    static URIAuthority parse(final CharSequence s, final Tokenizer.Cursor cursor) throws URISyntaxException {
+        final Tokenizer tokenizer = Tokenizer.INSTANCE;
+        String userInfo = null;
+        String hostName = null;
+        String portText = null;
+        final String token = tokenizer.parseContent(s, cursor, HOST_SEPARATORS);
+        if (!cursor.atEnd()) {
+            final char separator1 = s.charAt(cursor.getPos());
+            if (separator1 == '@') {
+                userInfo = !TextUtils.isEmpty(token) ? token : null;
+                cursor.updatePos(cursor.getPos() + 1);
+                hostName = tokenizer.parseContent(s, cursor, PORT_SEPARATORS);
+                if (!cursor.atEnd()) {
+                    final char separator2 = s.charAt(cursor.getPos());
+                    if (separator2 == ':') {
+                        cursor.updatePos(cursor.getPos() + 1);
+                        portText = tokenizer.parseContent(s, cursor, TERMINATORS);
+                    }
+                }
+            } else {
+                hostName = token;
+                if (separator1 == ':') {
+                    cursor.updatePos(cursor.getPos() + 1);
+                    portText = tokenizer.parseContent(s, cursor, TERMINATORS);
+                }
+            }
+        } else {
+            hostName = token;
+        }
+        if (TextUtils.isBlank(hostName)) {
+            throw createException(s, cursor, "Authority host is empty");
+        }
+        final int port;
+        if (!TextUtils.isBlank(portText)) {
+            try {
+                port = Integer.parseInt(portText);
+            } catch (final NumberFormatException ex) {
+                throw createException(s, cursor, "Authority port is invalid");
+            }
+        } else {
+            port = -1;
+        }
+        return new URIAuthority(userInfo, hostName.toLowerCase(Locale.ROOT), port, true);
+    }
+
+    static URIAuthority parse(final CharSequence s) throws URISyntaxException {
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
+        return parse(s, cursor);
+    }
+
+    static void format(final StringBuilder buf, final URIAuthority uriAuthority) {
+        if (uriAuthority.userInfo != null) {
+            buf.append(uriAuthority.userInfo);
+            buf.append("@");
+        }
+        buf.append(uriAuthority.hostname);
+        if (uriAuthority.port != -1) {
+            buf.append(":");
+            buf.append(uriAuthority.port);
+        }
+    }
+
+    static String format(final URIAuthority uriAuthority) {
+        final StringBuilder buf = new StringBuilder();
+        format(buf, uriAuthority);
+        return buf.toString();
+    }
+
     /**
      * @throws IllegalArgumentException
      *             If the port parameter is outside the specified range of valid port values, which is between 0 and
@@ -90,33 +183,15 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
      * Creates {@code URIHost} instance from string. Text may not contain any blanks.
      */
     public static URIAuthority create(final String s) throws URISyntaxException {
-        if (s == null) {
+        if (TextUtils.isBlank(s)) {
             return null;
         }
-        String userInfo = null;
-        String hostname = s;
-        int port = -1;
-        final int portIdx = hostname.lastIndexOf(":");
-        if (portIdx > 0) {
-            try {
-                port = Integer.parseInt(hostname.substring(portIdx + 1));
-            } catch (final NumberFormatException ex) {
-                throw new URISyntaxException(s, "invalid port");
-            }
-            hostname = hostname.substring(0, portIdx);
-        }
-        final int atIdx = hostname.lastIndexOf("@");
-        if (atIdx > 0) {
-            userInfo = hostname.substring(0, atIdx);
-            if (TextUtils.containsBlanks(userInfo)) {
-                throw new URISyntaxException(s, "user info contains blanks");
-            }
-            hostname = hostname.substring(atIdx + 1);
-        }
-        if (TextUtils.containsBlanks(hostname)) {
-            throw new URISyntaxException(s, "hostname contains blanks");
+        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
+        final URIAuthority uriAuthority = parse(s, cursor);
+        if (!cursor.atEnd()) {
+            throw createException(s, cursor, "Unexpected content");
         }
-        return new URIAuthority(userInfo, hostname.toLowerCase(Locale.ROOT), port, true);
+        return uriAuthority;
     }
 
     public URIAuthority(final String hostname) {
@@ -139,17 +214,7 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
 
     @Override
     public String toString() {
-        final StringBuilder buffer = new StringBuilder();
-        if (userInfo != null) {
-            buffer.append(userInfo);
-            buffer.append("@");
-        }
-        buffer.append(hostname);
-        if (port != -1) {
-            buffer.append(":");
-            buffer.append(Integer.toString(port));
-        }
-        return buffer.toString();
+        return format(this);
     }
 
     @Override
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 570e1d6..76c3b41 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
@@ -33,6 +33,8 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.net.URISyntaxException;
 
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -118,19 +120,91 @@ public class TestURIAuthority {
     }
 
     @Test
+    public void testParse() throws Exception {
+        MatcherAssert.assertThat(URIAuthority.parse("somehost"),
+                CoreMatchers.equalTo(new URIAuthority("somehost", -1)));
+        MatcherAssert.assertThat(URIAuthority.parse("somehost/blah"),
+                CoreMatchers.equalTo(new URIAuthority("somehost", -1)));
+        MatcherAssert.assertThat(URIAuthority.parse("somehost?blah"),
+                CoreMatchers.equalTo(new URIAuthority("somehost", -1)));
+        MatcherAssert.assertThat(URIAuthority.parse("somehost#blah"),
+                CoreMatchers.equalTo(new URIAuthority("somehost", -1)));
+        try {
+            URIAuthority.create("aaaa@:8080");
+            Assert.fail("URISyntaxException expected");
+        } catch (final URISyntaxException expected) {
+        }
+        try {
+            URIAuthority.create("@:");
+            Assert.fail("URISyntaxException expected");
+        } catch (final URISyntaxException expected) {
+        }
+        MatcherAssert.assertThat(URIAuthority.parse("somehost:8080"),
+                CoreMatchers.equalTo(new URIAuthority("somehost", 8080)));
+        MatcherAssert.assertThat(URIAuthority.parse("somehost:8080/blah"),
+                CoreMatchers.equalTo(new URIAuthority("somehost", 8080)));
+        MatcherAssert.assertThat(URIAuthority.parse("somehost:8080?blah"),
+                CoreMatchers.equalTo(new URIAuthority("somehost", 8080)));
+        MatcherAssert.assertThat(URIAuthority.parse("somehost:8080#blah"),
+                CoreMatchers.equalTo(new URIAuthority("somehost", 8080)));
+        MatcherAssert.assertThat(URIAuthority.parse("somehost:008080"),
+                CoreMatchers.equalTo(new URIAuthority("somehost", 8080)));
+        try {
+            URIAuthority.create("somehost:aaaaa");
+            Assert.fail("URISyntaxException expected");
+        } catch (final URISyntaxException expected) {
+        }
+        try {
+            URIAuthority.create("somehost:90ab");
+            Assert.fail("URISyntaxException expected");
+        } catch (final URISyntaxException expected) {
+        }
+
+        MatcherAssert.assertThat(URIAuthority.parse("someuser@somehost"),
+                CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1)));
+        MatcherAssert.assertThat(URIAuthority.parse("someuser@somehost/blah"),
+                CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1)));
+        MatcherAssert.assertThat(URIAuthority.parse("someuser@somehost?blah"),
+                CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1)));
+        MatcherAssert.assertThat(URIAuthority.parse("someuser@somehost#blah"),
+                CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1)));
+
+        MatcherAssert.assertThat(URIAuthority.parse("someuser@somehost:"),
+                CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1)));
+        MatcherAssert.assertThat(URIAuthority.parse("someuser@somehost:/blah"),
+                CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1)));
+        MatcherAssert.assertThat(URIAuthority.parse("someuser@somehost:?blah"),
+                CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1)));
+        MatcherAssert.assertThat(URIAuthority.parse("someuser@somehost:#blah"),
+                CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1)));
+
+        MatcherAssert.assertThat(URIAuthority.parse("someuser@somehost:8080"),
+                CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", 8080)));
+        MatcherAssert.assertThat(URIAuthority.parse("someuser@somehost:8080/blah"),
+                CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", 8080)));
+        MatcherAssert.assertThat(URIAuthority.parse("someuser@somehost:8080?blah"),
+                CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", 8080)));
+        MatcherAssert.assertThat(URIAuthority.parse("someuser@somehost:8080#blah"),
+                CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", 8080)));
+        MatcherAssert.assertThat(URIAuthority.parse("@somehost:8080"),
+                CoreMatchers.equalTo(new URIAuthority("somehost", 8080)));
+    }
+
+    @Test
     public void testCreateFromString() throws Exception {
         Assert.assertEquals(new URIAuthority("somehost", 8080), URIAuthority.create("somehost:8080"));
-        Assert.assertEquals(new URIAuthority("somehost", 8080), URIAuthority.create("SomeHost:8080"));
+        Assert.assertEquals(new URIAuthority("SomeHost", 8080), URIAuthority.create("SomeHost:8080"));
         Assert.assertEquals(new URIAuthority("somehost", 1234), URIAuthority.create("somehost:1234"));
         Assert.assertEquals(new URIAuthority("somehost", -1), URIAuthority.create("somehost"));
         Assert.assertEquals(new URIAuthority("user", "somehost", -1), URIAuthority.create("user@somehost"));
-    }
-
-    @Test
-    public void testCreateFromStringInvalid() throws Exception {
         try {
-            URIAuthority.create(" host ");
-            Assert.fail("IllegalArgumentException expected");
+            URIAuthority.create(" host");
+            Assert.fail("URISyntaxException expected");
+        } catch (final URISyntaxException expected) {
+        }
+        try {
+            URIAuthority.create("host  ");
+            Assert.fail("URISyntaxException expected");
         } catch (final URISyntaxException expected) {
         }
         try {


[httpcomponents-core] 15/18: HTTPCORE-645: Increase blocking default chunk size from 2 KiB to 8 KiB

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit fa128ec753ab0476de9639d6c1962eb54b0462d9
Author: Carter Kozak <c4...@gmail.com>
AuthorDate: Wed Aug 12 17:22:24 2020 -0400

    HTTPCORE-645: Increase blocking default chunk size from 2 KiB to 8 KiB
    
    This value matches the default session buffer size and avoids
    excessive memory copying between the chunk buffer and session
    buffer.
---
 .../main/java/org/apache/hc/core5/http/impl/io/BHttpConnectionBase.java | 2 +-
 .../main/java/org/apache/hc/core5/http/impl/io/ChunkedOutputStream.java | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

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 95168ad..ecbb010 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
@@ -158,7 +158,7 @@ class BHttpConnectionBase implements BHttpConnection {
     private byte[] getChunkedRequestBuffer() {
         if (chunkedRequestBuffer == null) {
             final int chunkSizeHint = this.http1Config.getChunkSizeHint();
-            chunkedRequestBuffer = new byte[chunkSizeHint > 0 ? chunkSizeHint : 2048];
+            chunkedRequestBuffer = new byte[chunkSizeHint > 0 ? chunkSizeHint : 8192];
         }
         return chunkedRequestBuffer;
     }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedOutputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedOutputStream.java
index b8ab5fd..23eb5e3 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedOutputStream.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedOutputStream.java
@@ -102,7 +102,7 @@ public class ChunkedOutputStream extends OutputStream {
             final OutputStream outputStream,
             final int chunkSizeHint,
             final Supplier<List<? extends Header>> trailerSupplier) {
-        this(buffer, outputStream, new byte[chunkSizeHint > 0 ? chunkSizeHint : 2048], trailerSupplier);
+        this(buffer, outputStream, new byte[chunkSizeHint > 0 ? chunkSizeHint : 8192], trailerSupplier);
     }
 
     /**


[httpcomponents-core] 03/18: Improved TLS configuration of async server endpoints

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 49591ef92cf6900e71d77e3c84e660b57dd25e1a
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sun Apr 12 11:03:26 2020 +0200

    Improved TLS configuration of async server endpoints
---
 .../nio/ClientHttpProtocolNegotiatorFactory.java   |  18 +++-
 .../nio/ServerHttpProtocolNegotiatorFactory.java   |  27 +++++-
 .../core5/http2/examples/H2FileServerExample.java  |   3 +-
 .../http2/examples/H2FullDuplexServerExample.java  |   3 +-
 .../hc/core5/http2/examples/H2GreetingServer.java  |   3 +-
 .../examples/ReactiveFullDuplexServerExample.java  |   3 +-
 .../hc/core5/benchmark/BenchmarkToolTest.java      |   2 +-
 .../apache/hc/core5/testing/nio/H2AlpnTest.java    |   2 +-
 .../testing/nio/H2ProtocolNegotiationTest.java     |  13 +--
 .../nio/H2ServerAndMultiplexingRequesterTest.java  |   8 +-
 .../testing/nio/H2ServerAndRequesterTest.java      |   6 +-
 .../hc/core5/testing/nio/H2TLSIntegrationTest.java |  21 +++--
 .../core5/testing/nio/Http1AuthenticationTest.java |  11 ++-
 .../testing/nio/Http1ServerAndRequesterTest.java   |  10 +-
 .../core5/testing/reactive/ReactiveClientTest.java |   3 +-
 .../http/impl/bootstrap/HttpAsyncRequester.java    | 103 +++++++++++----------
 .../core5/http/impl/bootstrap/HttpAsyncServer.java |  44 +++++++++
 .../http/impl/nio/ClientHttp1IOEventHandler.java   |   2 +
 .../impl/nio/ClientHttp1IOEventHandlerFactory.java |  13 ++-
 ...IOEventHandler.java => EndpointParameters.java} |  34 ++++---
 .../http/impl/nio/ServerHttp1IOEventHandler.java   |   5 +-
 .../impl/nio/ServerHttp1IOEventHandlerFactory.java |  26 ++++--
 .../http/examples/AsyncFileServerExample.java      |   3 +-
 .../examples/AsyncFullDuplexServerExample.java     |   3 +-
 .../http/examples/AsyncReverseProxyExample.java    |   3 +-
 .../http/examples/AsyncServerFilterExample.java    |   3 +-
 26 files changed, 238 insertions(+), 134 deletions(-)

diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiatorFactory.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiatorFactory.java
index 18381df..0f63ae9 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiatorFactory.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiatorFactory.java
@@ -33,8 +33,10 @@ import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory;
+import org.apache.hc.core5.http.impl.nio.EndpointParameters;
 import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
 import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.net.NamedEndpoint;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
 import org.apache.hc.core5.reactor.ProtocolIOSession;
 import org.apache.hc.core5.util.Args;
@@ -70,23 +72,29 @@ public class ClientHttpProtocolNegotiatorFactory implements IOEventHandlerFactor
 
     @Override
     public ClientHttpProtocolNegotiator createHandler(final ProtocolIOSession ioSession, final Object attachment) {
-        if (tlsStrategy != null && ioSession.getInitialEndpoint() instanceof HttpHost) {
-            final HttpHost host = (HttpHost) ioSession.getInitialEndpoint();
-            if (URIScheme.HTTPS.same(host.getSchemeName())) {
+        HttpVersionPolicy endpointPolicy = versionPolicy;
+        if (attachment instanceof EndpointParameters) {
+            final NamedEndpoint endpoint = ioSession.getInitialEndpoint();
+            final EndpointParameters params = (EndpointParameters) attachment;
+            if (tlsStrategy != null && endpoint != null && URIScheme.HTTPS.same(params.scheme)) {
+                final HttpHost host = new HttpHost(params.scheme, endpoint.getHostName(), endpoint.getPort());
                 tlsStrategy.upgrade(
                         ioSession,
                         host,
                         ioSession.getLocalAddress(),
                         ioSession.getRemoteAddress(),
-                        attachment,
+                        params.attachment,
                         handshakeTimeout);
             }
+            if (params.attachment instanceof HttpVersionPolicy) {
+                endpointPolicy = (HttpVersionPolicy) params.attachment;
+            }
         }
         return new ClientHttpProtocolNegotiator(
                 ioSession,
                 http1StreamHandlerFactory,
                 http2StreamHandlerFactory,
-                attachment instanceof HttpVersionPolicy ? (HttpVersionPolicy) attachment : versionPolicy);
+                endpointPolicy);
     }
 
 }
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttpProtocolNegotiatorFactory.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttpProtocolNegotiatorFactory.java
index 074aad9..817a895 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttpProtocolNegotiatorFactory.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttpProtocolNegotiatorFactory.java
@@ -30,6 +30,8 @@ package org.apache.hc.core5.http2.impl.nio;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.impl.nio.EndpointParameters;
 import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory;
 import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
 import org.apache.hc.core5.http2.HttpVersionPolicy;
@@ -68,16 +70,35 @@ public class ServerHttpProtocolNegotiatorFactory implements IOEventHandlerFactor
 
     @Override
     public ServerHttpProtocolNegotiator createHandler(final ProtocolIOSession ioSession, final Object attachment) {
-        if (tlsStrategy != null) {
+        HttpVersionPolicy endpointPolicy = versionPolicy;
+        if (attachment instanceof EndpointParameters) {
+            final EndpointParameters params = (EndpointParameters) attachment;
+            if (tlsStrategy != null && URIScheme.HTTPS.same(params.scheme)) {
+                tlsStrategy.upgrade(
+                        ioSession,
+                        null,
+                        ioSession.getLocalAddress(),
+                        ioSession.getRemoteAddress(),
+                        params.attachment,
+                        handshakeTimeout);
+            }
+            if (params.attachment instanceof HttpVersionPolicy) {
+                endpointPolicy = (HttpVersionPolicy) params.attachment;
+            }
+        } else {
             tlsStrategy.upgrade(
                     ioSession,
                     null,
                     ioSession.getLocalAddress(),
                     ioSession.getRemoteAddress(),
-                    attachment != null ? attachment : versionPolicy,
+                    attachment,
                     handshakeTimeout);
         }
-        return new ServerHttpProtocolNegotiator(ioSession, http1StreamDuplexerFactory, http2StreamMultiplexerFactory, versionPolicy);
+        return new ServerHttpProtocolNegotiator(
+                ioSession,
+                http1StreamDuplexerFactory,
+                http2StreamMultiplexerFactory,
+                endpointPolicy);
     }
 
 }
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 eaca5be..daa975f 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
@@ -46,6 +46,7 @@ import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.Message;
 import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
 import org.apache.hc.core5.http.nio.AsyncRequestConsumer;
 import org.apache.hc.core5.http.nio.AsyncServerRequestHandler;
@@ -202,7 +203,7 @@ public class H2FileServerExample {
         });
 
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port), URIScheme.HTTP);
         final ListenerEndpoint listenerEndpoint = future.get();
         System.out.print("Listening on " + listenerEndpoint.getAddress());
         server.awaitShutdown(TimeValue.ofDays(Long.MAX_VALUE));
diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2FullDuplexServerExample.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2FullDuplexServerExample.java
index dc922a0..78e5f59 100644
--- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2FullDuplexServerExample.java
+++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2FullDuplexServerExample.java
@@ -42,6 +42,7 @@ import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
 import org.apache.hc.core5.http.message.BasicHttpResponse;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
@@ -235,7 +236,7 @@ public class H2FullDuplexServerExample {
         });
 
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port), URIScheme.HTTP);
         final ListenerEndpoint listenerEndpoint = future.get();
         System.out.print("Listening on " + listenerEndpoint.getAddress());
         server.awaitShutdown(TimeValue.ofDays(Long.MAX_VALUE));
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 2f5df7a..b1dc3e1 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
@@ -44,6 +44,7 @@ import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.Message;
 import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
 import org.apache.hc.core5.http.message.BasicHttpResponse;
 import org.apache.hc.core5.http.nio.AsyncEntityConsumer;
@@ -115,7 +116,7 @@ public class H2GreetingServer {
         }));
 
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port), URIScheme.HTTP);
         final ListenerEndpoint listenerEndpoint = future.get();
         System.out.println("Listening on " + listenerEndpoint.getAddress());
         server.awaitShutdown(TimeValue.ofDays(Long.MAX_VALUE));
diff --git a/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ReactiveFullDuplexServerExample.java b/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ReactiveFullDuplexServerExample.java
index 1e91980..5a91b6f 100644
--- a/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ReactiveFullDuplexServerExample.java
+++ b/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ReactiveFullDuplexServerExample.java
@@ -42,6 +42,7 @@ 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.URIScheme;
 import org.apache.hc.core5.http.impl.BasicEntityDetails;
 import org.apache.hc.core5.http.impl.Http1StreamListener;
 import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
@@ -141,7 +142,7 @@ public class ReactiveFullDuplexServerExample {
         });
 
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port), URIScheme.HTTP);
         final ListenerEndpoint listenerEndpoint = future.get();
         System.out.print("Listening on " + listenerEndpoint.getAddress());
         server.awaitShutdown(TimeValue.ofDays(Long.MAX_VALUE));
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 00f93fe..d3195d2 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
@@ -107,7 +107,7 @@ public class BenchmarkToolTest {
                 .setVersionPolicy(versionPolicy)
                 .create();
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTP);
         final ListenerEndpoint listener = future.get();
         address = (InetSocketAddress) listener.getAddress();
     }
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 f4b1b46..6f139da 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
@@ -181,7 +181,7 @@ public class H2AlpnTest {
     @Test
     public void testALPN() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
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 18f20cd..e8eccd2 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
@@ -41,6 +41,7 @@ import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.Message;
 import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.ProtocolVersion;
+import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
 import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
@@ -171,12 +172,12 @@ public class H2ProtocolNegotiationTest {
     @Test
     public void testForceHttp1() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
 
-        final HttpHost target = new HttpHost("https", "localhost", address.getPort());
+        final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
         final Future<AsyncClientEndpoint> connectFuture = requester.connect(target, TIMEOUT, HttpVersionPolicy.FORCE_HTTP_1, null);
         final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
 
@@ -194,12 +195,12 @@ public class H2ProtocolNegotiationTest {
     @Test
     public void testForceHttp2() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
 
-        final HttpHost target = new HttpHost("https", "localhost", address.getPort());
+        final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
         final Future<AsyncClientEndpoint> connectFuture = requester.connect(target, TIMEOUT, HttpVersionPolicy.FORCE_HTTP_2, null);
         final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
 
@@ -217,12 +218,12 @@ public class H2ProtocolNegotiationTest {
     @Test
     public void testNegotiateProtocol() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
 
-        final HttpHost target = new HttpHost("https", "localhost", address.getPort());
+        final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
         final Future<AsyncClientEndpoint> connectFuture = requester.connect(target, TIMEOUT, HttpVersionPolicy.NEGOTIATE, null);
         final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
 
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndMultiplexingRequesterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndMultiplexingRequesterTest.java
index e8ac72d..a572fce 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndMultiplexingRequesterTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndMultiplexingRequesterTest.java
@@ -187,7 +187,7 @@ public class H2ServerAndMultiplexingRequesterTest {
     @Test
     public void testSequentialRequests() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), scheme);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
@@ -230,7 +230,7 @@ public class H2ServerAndMultiplexingRequesterTest {
     @Test
     public void testMultiplexedRequests() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), scheme);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
@@ -265,7 +265,7 @@ public class H2ServerAndMultiplexingRequesterTest {
     @Test
     public void testValidityCheck() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), scheme);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
@@ -313,7 +313,7 @@ public class H2ServerAndMultiplexingRequesterTest {
     @Test
     public void testMultiplexedRequestCancellation() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), scheme);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndRequesterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndRequesterTest.java
index 832c185..88cc3ce 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndRequesterTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndRequesterTest.java
@@ -190,7 +190,7 @@ public class H2ServerAndRequesterTest {
     @Test
     public void testSequentialRequests() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), scheme);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
@@ -233,7 +233,7 @@ public class H2ServerAndRequesterTest {
     @Test
     public void testSequentialRequestsSameEndpoint() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), scheme);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
@@ -284,7 +284,7 @@ public class H2ServerAndRequesterTest {
     @Test
     public void testPipelinedRequests() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), scheme);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2TLSIntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2TLSIntegrationTest.java
index 5a0f53f..9bcfb9d 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2TLSIntegrationTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2TLSIntegrationTest.java
@@ -46,6 +46,7 @@ import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.Message;
 import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.ProtocolVersion;
+import org.apache.hc.core5.http.URIScheme;
 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;
@@ -164,12 +165,12 @@ public class H2TLSIntegrationTest {
                 .create();
 
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
 
-        final HttpHost target = new HttpHost("https", "localhost", address.getPort());
+        final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
         final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
                 new BasicRequestProducer(Method.POST, target, "/stuff",
                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
@@ -225,12 +226,12 @@ public class H2TLSIntegrationTest {
                 .create();
 
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
 
-        final HttpHost target = new HttpHost("https", "localhost", address.getPort());
+        final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
         final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
                 new BasicRequestProducer(Method.POST, target, "/stuff",
                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
@@ -291,12 +292,12 @@ public class H2TLSIntegrationTest {
                 .create();
 
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
 
-        final HttpHost target = new HttpHost("https", "localhost", address.getPort());
+        final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
         final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
                 new BasicRequestProducer(Method.POST, target, "/stuff",
                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
@@ -357,12 +358,12 @@ public class H2TLSIntegrationTest {
                 .create();
 
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
 
-        final HttpHost target = new HttpHost("https", "localhost", address.getPort());
+        final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
         final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
                 new BasicRequestProducer(Method.POST, target, "/stuff",
                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
@@ -442,11 +443,11 @@ public class H2TLSIntegrationTest {
                     .create();
             try {
                 server.start();
-                final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+                final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
                 final ListenerEndpoint listener = future.get();
                 final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
 
-                final HttpHost target = new HttpHost("https", "localhost", address.getPort());
+                final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
                 final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
                         new BasicRequestProducer(Method.POST, target, "/stuff",
                                 new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
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 554cf4d..8d12aa1 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
@@ -45,6 +45,7 @@ import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.Message;
 import org.apache.hc.core5.http.Method;
+import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap;
 import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
@@ -53,11 +54,11 @@ import org.apache.hc.core5.http.impl.bootstrap.StandardFilter;
 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.support.BasicRequestProducer;
-import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
 import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
 import org.apache.hc.core5.http.nio.support.AbstractAsyncServerAuthFilter;
+import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
+import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
 import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.io.CloseMode;
 import org.apache.hc.core5.net.URIAuthority;
@@ -204,7 +205,7 @@ public class Http1AuthenticationTest {
     @Test
     public void testGetRequestAuthentication() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTP);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
@@ -238,7 +239,7 @@ public class Http1AuthenticationTest {
     @Test
     public void testPostRequestAuthentication() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTP);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
@@ -276,7 +277,7 @@ public class Http1AuthenticationTest {
     @Test
     public void testPostRequestAuthenticationNoExpectContinue() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTP);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
index 484fa9d..1741d91 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
@@ -218,7 +218,7 @@ public class Http1ServerAndRequesterTest {
     @Test
     public void testSequentialRequests() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), scheme);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
@@ -261,7 +261,7 @@ public class Http1ServerAndRequesterTest {
     @Test
     public void testSequentialRequestsNonPersistentConnection() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), scheme);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
@@ -304,7 +304,7 @@ public class Http1ServerAndRequesterTest {
     @Test
     public void testSequentialRequestsSameEndpoint() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), scheme);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
@@ -355,7 +355,7 @@ public class Http1ServerAndRequesterTest {
     @Test
     public void testPipelinedRequests() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), scheme);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
@@ -398,7 +398,7 @@ public class Http1ServerAndRequesterTest {
     @Test
     public void testNonPersistentHeads() throws Exception {
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), scheme);
         final ListenerEndpoint listener = future.get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
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 d6511e1..20603d8 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
@@ -50,6 +50,7 @@ import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStreamResetException;
 import org.apache.hc.core5.http.Message;
 import org.apache.hc.core5.http.Method;
+import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
@@ -364,7 +365,7 @@ public class ReactiveClientTest {
 
     private InetSocketAddress startClientAndServer() throws InterruptedException, ExecutionException {
         server.start();
-        final ListenerEndpoint listener = server.listen(new InetSocketAddress(0)).get();
+        final ListenerEndpoint listener = server.listen(new InetSocketAddress(0), URIScheme.HTTP).get();
         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
         requester.start();
         return address;
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 defebdd..abafada 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,6 +49,7 @@ import org.apache.hc.core5.http.HttpRequest;
 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.nio.EndpointParameters;
 import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
 import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
 import org.apache.hc.core5.http.nio.AsyncPushConsumer;
@@ -99,7 +100,7 @@ public class HttpAsyncRequester extends AsyncRequester implements ConnPoolContro
             final IOSessionListener sessionListener,
             final ManagedConnPool<HttpHost, IOSession> connPool) {
         super(eventHandlerFactory, ioReactorConfig, ioSessionDecorator, exceptionCallback, sessionListener,
-                        ShutdownCommand.GRACEFUL_IMMEDIATE_CALLBACK, DefaultAddressResolver.INSTANCE);
+                ShutdownCommand.GRACEFUL_IMMEDIATE_CALLBACK, DefaultAddressResolver.INSTANCE);
         this.connPool = Args.notNull(connPool, "Connection pool");
     }
 
@@ -177,59 +178,63 @@ public class HttpAsyncRequester extends AsyncRequester implements ConnPoolContro
         final Future<PoolEntry<HttpHost, IOSession>> leaseFuture = connPool.lease(
                 host, null, timeout, new FutureCallback<PoolEntry<HttpHost, IOSession>>() {
 
-            @Override
-            public void completed(final PoolEntry<HttpHost, IOSession> poolEntry) {
-                final AsyncClientEndpoint endpoint = new InternalAsyncClientEndpoint(poolEntry);
-                final IOSession ioSession = poolEntry.getConnection();
-                if (ioSession != null && !ioSession.isOpen()) {
-                    poolEntry.discardConnection(CloseMode.IMMEDIATE);
-                }
-                if (poolEntry.hasConnection()) {
-                    resultFuture.completed(endpoint);
-                } else {
-                    final Future<IOSession> futute = requestSession(host, timeout, attachment, new FutureCallback<IOSession>() {
-
-                        @Override
-                        public void completed(final IOSession session) {
-                            session.setSocketTimeout(timeout);
-                            poolEntry.assignConnection(session);
-                            resultFuture.completed(endpoint);
-                        }
-
-                        @Override
-                        public void failed(final Exception cause) {
-                            try {
-                                resultFuture.failed(cause);
-                            } finally {
-                                endpoint.releaseAndDiscard();
-                            }
+                    @Override
+                    public void completed(final PoolEntry<HttpHost, IOSession> poolEntry) {
+                        final AsyncClientEndpoint endpoint = new InternalAsyncClientEndpoint(poolEntry);
+                        final IOSession ioSession = poolEntry.getConnection();
+                        if (ioSession != null && !ioSession.isOpen()) {
+                            poolEntry.discardConnection(CloseMode.IMMEDIATE);
                         }
-
-                        @Override
-                        public void cancelled() {
-                            try {
-                                resultFuture.cancel();
-                            } finally {
-                                endpoint.releaseAndDiscard();
-                            }
+                        if (poolEntry.hasConnection()) {
+                            resultFuture.completed(endpoint);
+                        } else {
+                            final Future<IOSession> future = requestSession(
+                                    host,
+                                    timeout,
+                                    new EndpointParameters(host.getSchemeName(), attachment),
+                                    new FutureCallback<IOSession>() {
+
+                                        @Override
+                                        public void completed(final IOSession session) {
+                                            session.setSocketTimeout(timeout);
+                                            poolEntry.assignConnection(session);
+                                            resultFuture.completed(endpoint);
+                                        }
+
+                                        @Override
+                                        public void failed(final Exception cause) {
+                                            try {
+                                                resultFuture.failed(cause);
+                                            } finally {
+                                                endpoint.releaseAndDiscard();
+                                            }
+                                        }
+
+                                        @Override
+                                        public void cancelled() {
+                                            try {
+                                                resultFuture.cancel();
+                                            } finally {
+                                                endpoint.releaseAndDiscard();
+                                            }
+                                        }
+
+                                    });
+                            resultFuture.setDependency(future);
                         }
+                    }
 
-                    });
-                    resultFuture.setDependency(futute);
-                }
-            }
-
-            @Override
-            public void failed(final Exception ex) {
-                resultFuture.failed(ex);
-            }
+                    @Override
+                    public void failed(final Exception ex) {
+                        resultFuture.failed(ex);
+                    }
 
-            @Override
-            public void cancelled() {
-                resultFuture.cancel();
-            }
+                    @Override
+                    public void cancelled() {
+                        resultFuture.cancel();
+                    }
 
-        });
+                });
         resultFuture.setDependency(leaseFuture);
         return resultFuture;
     }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncServer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncServer.java
index 72e5bd6..1e39f18 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncServer.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncServer.java
@@ -26,14 +26,21 @@
  */
 package org.apache.hc.core5.http.impl.bootstrap;
 
+import java.net.SocketAddress;
+import java.util.concurrent.Future;
+
 import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.concurrent.FutureCallback;
 import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.function.Decorator;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.impl.nio.EndpointParameters;
 import org.apache.hc.core5.http.nio.command.ShutdownCommand;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.IOSession;
 import org.apache.hc.core5.reactor.IOSessionListener;
+import org.apache.hc.core5.reactor.ListenerEndpoint;
 
 /**
  * HTTP/1.1 server side message exchange handler.
@@ -56,4 +63,41 @@ public class HttpAsyncServer extends AsyncServer {
                         ShutdownCommand.GRACEFUL_NORMAL_CALLBACK);
     }
 
+    public Future<ListenerEndpoint> listen(
+            final SocketAddress address,
+            final URIScheme scheme,
+            final Object attachment,
+            final FutureCallback<ListenerEndpoint> callback) {
+        return super.listen(address, new EndpointParameters(scheme.id, attachment), callback);
+    }
+
+    public Future<ListenerEndpoint> listen(
+            final SocketAddress address,
+            final URIScheme scheme,
+            final FutureCallback<ListenerEndpoint> callback) {
+        return listen(address, scheme, null, callback);
+    }
+
+    public Future<ListenerEndpoint> listen(final SocketAddress address, final URIScheme scheme) {
+        return listen(address, scheme, null, null);
+    }
+
+    /**
+     * @deprecated Use {@link #listen(SocketAddress, URIScheme, FutureCallback)}
+     */
+    @Deprecated
+    @Override
+    public Future<ListenerEndpoint> listen(final SocketAddress address, final FutureCallback<ListenerEndpoint> callback) {
+        return super.listen(address, callback);
+    }
+
+    /**
+     * @deprecated Use {@link #listen(SocketAddress, URIScheme)}
+     */
+    @Deprecated
+    @Override
+    public Future<ListenerEndpoint> listen(final SocketAddress address) {
+        return super.listen(address);
+    }
+
 }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandler.java
index 0ed0a6e..cd23cb8 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandler.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandler.java
@@ -27,6 +27,7 @@
 
 package org.apache.hc.core5.http.impl.nio;
 
+import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.net.InetAddressUtils;
 
 /**
@@ -36,6 +37,7 @@ import org.apache.hc.core5.net.InetAddressUtils;
  *
  * @since 5.0
  */
+@Internal
 public class ClientHttp1IOEventHandler extends AbstractHttp1IOEventHandler {
 
     public ClientHttp1IOEventHandler(final ClientHttp1StreamDuplexer streamDuplexer) {
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandlerFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandlerFactory.java
index 9c215a7..2a8b571 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandlerFactory.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandlerFactory.java
@@ -28,10 +28,12 @@
 package org.apache.hc.core5.http.impl.nio;
 
 import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
+import org.apache.hc.core5.net.NamedEndpoint;
 import org.apache.hc.core5.reactor.IOEventHandler;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
 import org.apache.hc.core5.reactor.ProtocolIOSession;
@@ -44,6 +46,7 @@ import org.apache.hc.core5.util.Timeout;
  * @since 5.0
  */
 @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
+@Internal
 public class ClientHttp1IOEventHandlerFactory implements IOEventHandlerFactory {
 
     private final ClientHttp1StreamDuplexerFactory streamDuplexerFactory;
@@ -61,15 +64,17 @@ public class ClientHttp1IOEventHandlerFactory implements IOEventHandlerFactory {
 
     @Override
     public IOEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) {
-        if (tlsStrategy != null && ioSession.getInitialEndpoint() instanceof HttpHost) {
-            final HttpHost host = (HttpHost) ioSession.getInitialEndpoint();
-            if (URIScheme.HTTPS.same(host.getSchemeName())) {
+        if (attachment instanceof EndpointParameters) {
+            final EndpointParameters params = (EndpointParameters) attachment;
+            final NamedEndpoint endpoint = ioSession.getInitialEndpoint();
+            if (tlsStrategy != null && endpoint != null && URIScheme.HTTPS.same(params.scheme)) {
+                final HttpHost host = new HttpHost(params.scheme, endpoint.getHostName(), endpoint.getPort());
                 tlsStrategy.upgrade(
                         ioSession,
                         host,
                         ioSession.getLocalAddress(),
                         ioSession.getRemoteAddress(),
-                        attachment,
+                        params.attachment,
                         handshakeTimeout);
             }
         }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/EndpointParameters.java
similarity index 62%
copy from httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandler.java
copy to httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/EndpointParameters.java
index 0ed0a6e..2a3d54d 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandler.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/EndpointParameters.java
@@ -24,35 +24,33 @@
  * <http://www.apache.org/>.
  *
  */
-
 package org.apache.hc.core5.http.impl.nio;
 
-import org.apache.hc.core5.net.InetAddressUtils;
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.http.URIScheme;
 
 /**
- * {@link org.apache.hc.core5.reactor.IOEventHandler} that implements
- *  client side HTTP/1.1 messaging protocol with full support for
- *  duplexed message transmission and message pipelining.
+ * Endpoint initialization parameters
  *
- * @since 5.0
+ * @since 5.1
  */
-public class ClientHttp1IOEventHandler extends AbstractHttp1IOEventHandler {
+@Internal
+public final class EndpointParameters {
+
+    public final String scheme;
+    public final Object attachment;
 
-    public ClientHttp1IOEventHandler(final ClientHttp1StreamDuplexer streamDuplexer) {
-        super(streamDuplexer);
+    public EndpointParameters(final String scheme, final Object attachment) {
+        this.scheme = scheme != null ? scheme : URIScheme.HTTP.id;
+        this.attachment = attachment;
     }
 
     @Override
     public String toString() {
-        final StringBuilder buf = new StringBuilder();
-        InetAddressUtils.formatAddress(buf, getLocalAddress());
-        buf.append("->");
-        InetAddressUtils.formatAddress(buf, getRemoteAddress());
-        buf.append(" [");
-        streamDuplexer.appendState(buf);
-        buf.append("]");
-        return buf.toString();
+        return "EndpointParameters{" +
+                "scheme=" + scheme +
+                ", attachment=" + attachment +
+                '}';
     }
 
 }
-
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1IOEventHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1IOEventHandler.java
index e2e0d97..e2ffa60 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1IOEventHandler.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1IOEventHandler.java
@@ -27,8 +27,7 @@
 
 package org.apache.hc.core5.http.impl.nio;
 
-import org.apache.hc.core5.annotation.Contract;
-import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.net.InetAddressUtils;
 
 /**
@@ -38,7 +37,7 @@ import org.apache.hc.core5.net.InetAddressUtils;
  *
  * @since 5.0
  */
-@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
+@Internal
 public class ServerHttp1IOEventHandler extends AbstractHttp1IOEventHandler {
 
     public ServerHttp1IOEventHandler(final ServerHttp1StreamDuplexer streamDuplexer) {
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1IOEventHandlerFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1IOEventHandlerFactory.java
index 0da7931..09ece66 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1IOEventHandlerFactory.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1IOEventHandlerFactory.java
@@ -28,6 +28,7 @@
 package org.apache.hc.core5.http.impl.nio;
 
 import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
@@ -43,6 +44,7 @@ import org.apache.hc.core5.util.Timeout;
  * @since 5.0
  */
 @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
+@Internal
 public class ServerHttp1IOEventHandlerFactory implements IOEventHandlerFactory {
 
     private final ServerHttp1StreamDuplexerFactory streamDuplexerFactory;
@@ -60,21 +62,29 @@ public class ServerHttp1IOEventHandlerFactory implements IOEventHandlerFactory {
 
     @Override
     public IOEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) {
-        final boolean tlsSecured;
-        if (tlsStrategy != null) {
-            tlsSecured = tlsStrategy.upgrade(
+        String endpointScheme = URIScheme.HTTP.id;
+        if (attachment instanceof EndpointParameters) {
+            final EndpointParameters params = (EndpointParameters) attachment;
+            endpointScheme = params.scheme;
+            if (tlsStrategy != null && URIScheme.HTTPS.same(endpointScheme)) {
+                tlsStrategy.upgrade(
+                        ioSession,
+                        null,
+                        ioSession.getLocalAddress(),
+                        ioSession.getRemoteAddress(),
+                        params.attachment,
+                        handshakeTimeout);
+            }
+        } else {
+            tlsStrategy.upgrade(
                     ioSession,
                     null,
                     ioSession.getLocalAddress(),
                     ioSession.getRemoteAddress(),
                     attachment,
                     handshakeTimeout);
-        } else {
-            tlsSecured = false;
         }
-        return new ServerHttp1IOEventHandler(streamDuplexerFactory.create(
-                tlsSecured ? URIScheme.HTTPS.id : URIScheme.HTTP.id,
-                ioSession));
+        return new ServerHttp1IOEventHandler(streamDuplexerFactory.create(endpointScheme, ioSession));
     }
 
 }
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 a8b776c..7b658d1 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
@@ -43,6 +43,7 @@ import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.Message;
 import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
 import org.apache.hc.core5.http.nio.AsyncRequestConsumer;
@@ -168,7 +169,7 @@ public class AsyncFileServerExample {
         });
 
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port), URIScheme.HTTP);
         final ListenerEndpoint listenerEndpoint = future.get();
         println("Listening on " + listenerEndpoint.getAddress());
         server.awaitShutdown(TimeValue.MAX_VALUE);
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncFullDuplexServerExample.java b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncFullDuplexServerExample.java
index 8b1b36a..12dd394 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncFullDuplexServerExample.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncFullDuplexServerExample.java
@@ -42,6 +42,7 @@ import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.impl.Http1StreamListener;
 import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
@@ -216,7 +217,7 @@ public class AsyncFullDuplexServerExample {
         });
 
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port), URIScheme.HTTP);
         final ListenerEndpoint listenerEndpoint = future.get();
         System.out.print("Listening on " + listenerEndpoint.getAddress());
         server.awaitShutdown(TimeValue.MAX_VALUE);
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 da0acb1..8136cab 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
@@ -56,6 +56,7 @@ import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.impl.BasicEntityDetails;
 import org.apache.hc.core5.http.impl.Http1StreamListener;
 import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap;
@@ -204,7 +205,7 @@ public class AsyncReverseProxyExample {
 
         requester.start();
         server.start();
-        server.listen(new InetSocketAddress(port));
+        server.listen(new InetSocketAddress(port), URIScheme.HTTP);
         println("Listening on port " + port);
 
         server.awaitShutdown(TimeValue.MAX_VALUE);
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncServerFilterExample.java b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncServerFilterExample.java
index fd8c6b0..216bbd9 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncServerFilterExample.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncServerFilterExample.java
@@ -37,6 +37,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.Message;
+import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
 import org.apache.hc.core5.http.impl.bootstrap.StandardFilter;
@@ -188,7 +189,7 @@ public class AsyncServerFilterExample {
         });
 
         server.start();
-        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port));
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port), URIScheme.HTTP);
         final ListenerEndpoint listenerEndpoint = future.get();
         System.out.print("Listening on " + listenerEndpoint.getAddress());
         server.awaitShutdown(TimeValue.MAX_VALUE);


[httpcomponents-core] 04/18: Deprecated SecurePortStrategy

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit b44e1da29284bf1124e8150d01174539f72f10c4
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sun Apr 12 16:01:59 2020 +0200

    Deprecated SecurePortStrategy
---
 .../impl/nio/bootstrap/H2ServerBootstrap.java      |  3 +-
 .../http2/ssl/ConscryptServerTlsStrategy.java      | 54 ++++++++++++++++++-
 .../hc/core5/http2/ssl/H2ServerTlsStrategy.java    | 58 ++++++++++++++++++++-
 .../apache/hc/core5/testing/nio/H2AlpnTest.java    |  4 +-
 .../testing/nio/H2ProtocolNegotiationTest.java     |  6 +--
 .../nio/H2ServerAndMultiplexingRequesterTest.java  |  5 +-
 .../testing/nio/H2ServerAndRequesterTest.java      |  5 +-
 .../hc/core5/testing/nio/H2TLSIntegrationTest.java |  7 +--
 .../testing/nio/Http1ServerAndRequesterTest.java   |  5 +-
 .../core5/testing/nio/SecureAllPortsStrategy.java  | 43 ----------------
 .../core5/http/nio/ssl/BasicServerTlsStrategy.java | 60 +++++++++++++++++++++-
 .../hc/core5/http/nio/ssl/FixedPortStrategy.java   |  3 ++
 .../hc/core5/http/nio/ssl/SecurePortStrategy.java  |  3 ++
 13 files changed, 188 insertions(+), 68 deletions(-)

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 6b2c8d9..e458a91 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
@@ -66,7 +66,6 @@ import org.apache.hc.core5.http2.impl.H2Processors;
 import org.apache.hc.core5.http2.impl.nio.H2StreamListener;
 import org.apache.hc.core5.http2.impl.nio.ServerH2StreamMultiplexerFactory;
 import org.apache.hc.core5.http2.impl.nio.ServerHttpProtocolNegotiatorFactory;
-import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy;
 import org.apache.hc.core5.net.InetAddressUtils;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
 import org.apache.hc.core5.reactor.IOReactorConfig;
@@ -440,7 +439,7 @@ public class H2ServerBootstrap {
                 http1StreamHandlerFactory,
                 http2StreamHandlerFactory,
                 versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE,
-                tlsStrategy != null ? tlsStrategy : new H2ServerTlsStrategy(443, 8443),
+                tlsStrategy,
                 handshakeTimeout);
         return new HttpAsyncServer(ioEventHandlerFactory, ioReactorConfig, ioSessionDecorator, exceptionCallback,
                 sessionListener);
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/ConscryptServerTlsStrategy.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/ConscryptServerTlsStrategy.java
index a93a373..dba69b8 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/ConscryptServerTlsStrategy.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/ConscryptServerTlsStrategy.java
@@ -51,11 +51,16 @@ import org.apache.hc.core5.util.Timeout;
 public class ConscryptServerTlsStrategy implements TlsStrategy {
 
     private final SSLContext sslContext;
+    @SuppressWarnings("deprecation")
     private final SecurePortStrategy securePortStrategy;
     private final SSLBufferMode sslBufferMode;
     private final SSLSessionInitializer initializer;
     private final SSLSessionVerifier verifier;
 
+    /**
+     * @deprecated Use {@link ConscryptServerTlsStrategy#ConscryptServerTlsStrategy(SSLContext, SSLBufferMode, SSLSessionInitializer, SSLSessionVerifier)}
+     */
+    @Deprecated
     public ConscryptServerTlsStrategy(
             final SSLContext sslContext,
             final SecurePortStrategy securePortStrategy,
@@ -69,6 +74,10 @@ public class ConscryptServerTlsStrategy implements TlsStrategy {
         this.verifier = verifier;
     }
 
+    /**
+     * @deprecated Use {@link ConscryptServerTlsStrategy#ConscryptServerTlsStrategy(SSLContext, SSLSessionInitializer, SSLSessionVerifier)}
+     */
+    @Deprecated
     public ConscryptServerTlsStrategy(
             final SSLContext sslContext,
             final SecurePortStrategy securePortStrategy,
@@ -77,6 +86,10 @@ public class ConscryptServerTlsStrategy implements TlsStrategy {
         this(sslContext, securePortStrategy, null, initializer, verifier);
     }
 
+    /**
+     * @deprecated Use {@link ConscryptServerTlsStrategy#ConscryptServerTlsStrategy(SSLContext, SSLSessionVerifier)}
+     */
+    @Deprecated
     public ConscryptServerTlsStrategy(
             final SSLContext sslContext,
             final SecurePortStrategy securePortStrategy,
@@ -84,14 +97,53 @@ public class ConscryptServerTlsStrategy implements TlsStrategy {
         this(sslContext, securePortStrategy, null, null, verifier);
     }
 
+    /**
+     * @deprecated Use {@link ConscryptServerTlsStrategy#ConscryptServerTlsStrategy(SSLContext)}
+     */
+    @Deprecated
     public ConscryptServerTlsStrategy(final SSLContext sslContext, final SecurePortStrategy securePortStrategy) {
         this(sslContext, securePortStrategy, null, null, null);
     }
 
+    /**
+     * @deprecated Use {@link ConscryptServerTlsStrategy#ConscryptServerTlsStrategy(SSLContext)}
+     */
+    @Deprecated
     public ConscryptServerTlsStrategy(final SSLContext sslContext, final int... securePorts) {
         this(sslContext, new FixedPortStrategy(securePorts));
     }
 
+    public ConscryptServerTlsStrategy(
+            final SSLContext sslContext,
+            final SSLBufferMode sslBufferMode,
+            final SSLSessionInitializer initializer,
+            final SSLSessionVerifier verifier) {
+        this.sslContext = Args.notNull(sslContext, "SSL context");
+        this.sslBufferMode = sslBufferMode;
+        this.initializer = initializer;
+        this.verifier = verifier;
+        this.securePortStrategy = null;
+    }
+
+    public ConscryptServerTlsStrategy(
+            final SSLContext sslContext,
+            final SSLSessionInitializer initializer,
+            final SSLSessionVerifier verifier) {
+        this(sslContext, (SSLBufferMode) null, initializer, verifier);
+    }
+
+    public ConscryptServerTlsStrategy(final SSLContext sslContext, final SSLSessionVerifier verifier) {
+        this(sslContext, (SSLBufferMode) null, null, verifier);
+    }
+
+    public ConscryptServerTlsStrategy(final SSLContext sslContext) {
+        this(sslContext, (SSLBufferMode) null, null, null);
+    }
+
+    private boolean isApplicable(final SocketAddress localAddress) {
+        return securePortStrategy == null || securePortStrategy.isSecure(localAddress);
+    }
+
     @Override
     public boolean upgrade(
             final TransportSecurityLayer tlsSession,
@@ -100,7 +152,7 @@ public class ConscryptServerTlsStrategy implements TlsStrategy {
             final SocketAddress remoteAddress,
             final Object attachment,
             final Timeout handshakeTimeout) {
-        if (securePortStrategy != null && securePortStrategy.isSecure(localAddress)) {
+        if (isApplicable(localAddress)) {
             tlsSession.startTls(
                     sslContext,
                     host,
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2ServerTlsStrategy.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2ServerTlsStrategy.java
index 1b1d4a7..f4e7f6f 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2ServerTlsStrategy.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2ServerTlsStrategy.java
@@ -52,11 +52,16 @@ import org.apache.hc.core5.util.Timeout;
 public class H2ServerTlsStrategy implements TlsStrategy {
 
     private final SSLContext sslContext;
+    @SuppressWarnings("deprecation")
     private final SecurePortStrategy securePortStrategy;
     private final SSLBufferMode sslBufferMode;
     private final SSLSessionInitializer initializer;
     private final SSLSessionVerifier verifier;
 
+    /**
+     * @deprecated Use {@link H2ServerTlsStrategy#H2ServerTlsStrategy(SSLContext, SSLBufferMode, SSLSessionInitializer, SSLSessionVerifier)}
+     */
+    @Deprecated
     public H2ServerTlsStrategy(
             final SSLContext sslContext,
             final SecurePortStrategy securePortStrategy,
@@ -70,6 +75,10 @@ public class H2ServerTlsStrategy implements TlsStrategy {
         this.verifier = verifier;
     }
 
+    /**
+     * @deprecated Use {@link H2ServerTlsStrategy#H2ServerTlsStrategy(SSLContext, SSLSessionInitializer, SSLSessionVerifier)}
+     */
+    @Deprecated
     public H2ServerTlsStrategy(
             final SSLContext sslContext,
             final SecurePortStrategy securePortStrategy,
@@ -78,6 +87,10 @@ public class H2ServerTlsStrategy implements TlsStrategy {
         this(sslContext, securePortStrategy, null, initializer, verifier);
     }
 
+    /**
+     * @deprecated Use {@link H2ServerTlsStrategy#H2ServerTlsStrategy(SSLContext, SSLSessionVerifier)}
+     */
+    @Deprecated
     public H2ServerTlsStrategy(
             final SSLContext sslContext,
             final SecurePortStrategy securePortStrategy,
@@ -85,14 +98,57 @@ public class H2ServerTlsStrategy implements TlsStrategy {
         this(sslContext, securePortStrategy, null, null, verifier);
     }
 
+    /**
+     * @deprecated Use {@link H2ServerTlsStrategy#H2ServerTlsStrategy(SSLContext)}
+     */
+    @Deprecated
     public H2ServerTlsStrategy(final SSLContext sslContext, final SecurePortStrategy securePortStrategy) {
         this(sslContext, securePortStrategy, null, null, null);
     }
 
+    /**
+     * @deprecated Use {@link H2ServerTlsStrategy#H2ServerTlsStrategy()}
+     */
+    @Deprecated
     public H2ServerTlsStrategy(final int... securePorts) {
         this(SSLContexts.createSystemDefault(), new FixedPortStrategy(securePorts));
     }
 
+    public H2ServerTlsStrategy(
+            final SSLContext sslContext,
+            final SSLBufferMode sslBufferMode,
+            final SSLSessionInitializer initializer,
+            final SSLSessionVerifier verifier) {
+        this.sslContext = Args.notNull(sslContext, "SSL context");
+        this.sslBufferMode = sslBufferMode;
+        this.initializer = initializer;
+        this.verifier = verifier;
+        this.securePortStrategy = null;
+    }
+
+    public H2ServerTlsStrategy(
+            final SSLContext sslContext,
+            final SSLSessionInitializer initializer,
+            final SSLSessionVerifier verifier) {
+        this(sslContext, (SSLBufferMode) null, initializer, verifier);
+    }
+
+    public H2ServerTlsStrategy(final SSLContext sslContext, final SSLSessionVerifier verifier) {
+        this(sslContext, (SSLBufferMode) null, null, verifier);
+    }
+
+    public H2ServerTlsStrategy(final SSLContext sslContext) {
+        this(sslContext, (SSLBufferMode) null, null, null);
+    }
+
+    public H2ServerTlsStrategy() {
+        this(SSLContexts.createSystemDefault());
+    }
+
+    private boolean isApplicable(final SocketAddress localAddress) {
+        return securePortStrategy == null || securePortStrategy.isSecure(localAddress);
+    }
+
     @Override
     public boolean upgrade(
             final TransportSecurityLayer tlsSession,
@@ -101,7 +157,7 @@ public class H2ServerTlsStrategy implements TlsStrategy {
             final SocketAddress remoteAddress,
             final Object attachment,
             final Timeout handshakeTimeout) {
-        if (securePortStrategy != null && securePortStrategy.isSecure(localAddress)) {
+        if (isApplicable(localAddress)) {
             tlsSession.startTls(
                     sslContext,
                     host,
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 6f139da..f923c36 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
@@ -109,8 +109,8 @@ public class H2AlpnTest {
         protected void before() throws Throwable {
             log.debug("Starting up test server");
             final TlsStrategy tlsStrategy = h2Allowed ?
-                new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext(), SecureAllPortsStrategy.INSTANCE) :
-                new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext(), SecureAllPortsStrategy.INSTANCE);
+                new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext()) :
+                new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext());
             server = H2ServerBootstrap.bootstrap()
                     .setIOReactorConfig(
                             IOReactorConfig.custom()
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 e8eccd2..b77d390 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
@@ -88,15 +88,13 @@ public class H2ProtocolNegotiationTest {
         protected void before() throws Throwable {
             log.debug("Starting up test server");
             server = H2ServerBootstrap.bootstrap()
-                    .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext(), SecureAllPortsStrategy.INSTANCE))
+                    .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
                     .setVersionPolicy(HttpVersionPolicy.NEGOTIATE)
                     .setIOReactorConfig(
                             IOReactorConfig.custom()
                                     .setSoTimeout(TIMEOUT)
                                     .build())
-                    .setTlsStrategy(new H2ServerTlsStrategy(
-                            SSLTestContexts.createServerSSLContext(),
-                            SecureAllPortsStrategy.INSTANCE))
+                    .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
                     .register("*", new Supplier<AsyncServerExchangeHandler>() {
 
                         @Override
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndMultiplexingRequesterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndMultiplexingRequesterTest.java
index a572fce..6a60017 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndMultiplexingRequesterTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndMultiplexingRequesterTest.java
@@ -112,9 +112,8 @@ public class H2ServerAndMultiplexingRequesterTest {
                             IOReactorConfig.custom()
                                     .setSoTimeout(TIMEOUT)
                                     .build())
-                    .setTlsStrategy(scheme == URIScheme.HTTPS  ? new H2ServerTlsStrategy(
-                            SSLTestContexts.createServerSSLContext(),
-                            SecureAllPortsStrategy.INSTANCE) : null)
+                    .setTlsStrategy(scheme == URIScheme.HTTPS  ?
+                            new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext()) : null)
                     .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
                     .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndRequesterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndRequesterTest.java
index 88cc3ce..fc8a0d0 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndRequesterTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerAndRequesterTest.java
@@ -111,9 +111,8 @@ public class H2ServerAndRequesterTest {
                             IOReactorConfig.custom()
                                     .setSoTimeout(TIMEOUT)
                                     .build())
-                    .setTlsStrategy(scheme == URIScheme.HTTPS  ? new H2ServerTlsStrategy(
-                            SSLTestContexts.createServerSSLContext(),
-                            SecureAllPortsStrategy.INSTANCE) : null)
+                    .setTlsStrategy(scheme == URIScheme.HTTPS  ?
+                            new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext()) : null)
                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
                     .setStreamListener(LoggingH2StreamListener.INSTANCE)
                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2TLSIntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2TLSIntegrationTest.java
index 9bcfb9d..0833c57 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2TLSIntegrationTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2TLSIntegrationTest.java
@@ -123,7 +123,7 @@ public class H2TLSIntegrationTest {
                         IOReactorConfig.custom()
                                 .setSoTimeout(TIMEOUT)
                                 .build())
-                .setTlsStrategy(new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext(), SecureAllPortsStrategy.INSTANCE))
+                .setTlsStrategy(new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
                 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
@@ -197,7 +197,7 @@ public class H2TLSIntegrationTest {
                         IOReactorConfig.custom()
                                 .setSoTimeout(TIMEOUT)
                                 .build())
-                .setTlsStrategy(new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext(), SecureAllPortsStrategy.INSTANCE))
+                .setTlsStrategy(new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
                 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
@@ -255,7 +255,6 @@ public class H2TLSIntegrationTest {
                                 .build())
                 .setTlsStrategy(new BasicServerTlsStrategy(
                         SSLTestContexts.createServerSSLContext(),
-                        SecureAllPortsStrategy.INSTANCE,
                         new SSLSessionInitializer() {
 
                             @Override
@@ -321,7 +320,6 @@ public class H2TLSIntegrationTest {
                                 .build())
                 .setTlsStrategy(new BasicServerTlsStrategy(
                         SSLTestContexts.createServerSSLContext(),
-                        SecureAllPortsStrategy.INSTANCE,
                         new SSLSessionInitializer() {
 
                             @Override
@@ -419,7 +417,6 @@ public class H2TLSIntegrationTest {
                                     .build())
                     .setTlsStrategy(new BasicServerTlsStrategy(
                             SSLTestContexts.createServerSSLContext(),
-                            SecureAllPortsStrategy.INSTANCE,
                             new SSLSessionInitializer() {
 
                                 @Override
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
index 1741d91..2d961d7 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
@@ -164,9 +164,8 @@ public class Http1ServerAndRequesterTest {
                             });
                         }
                     })
-                    .setTlsStrategy(scheme == URIScheme.HTTPS  ? new BasicServerTlsStrategy(
-                            SSLTestContexts.createServerSSLContext(),
-                            SecureAllPortsStrategy.INSTANCE) : null)
+                    .setTlsStrategy(scheme == URIScheme.HTTPS  ?
+                            new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext()) : null)
                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
                     .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/SecureAllPortsStrategy.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/SecureAllPortsStrategy.java
deleted file mode 100644
index 16b697a..0000000
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/SecureAllPortsStrategy.java
+++ /dev/null
@@ -1,43 +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
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.hc.core5.testing.nio;
-
-import java.net.SocketAddress;
-
-import org.apache.hc.core5.http.nio.ssl.SecurePortStrategy;
-
-public class SecureAllPortsStrategy implements SecurePortStrategy {
-
-    public static final SecureAllPortsStrategy INSTANCE = new SecureAllPortsStrategy();
-
-    @Override
-    public boolean isSecure(final SocketAddress localAddress) {
-        return true;
-    }
-
-}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/BasicServerTlsStrategy.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/BasicServerTlsStrategy.java
index a10351e..06cd6b4 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/BasicServerTlsStrategy.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/BasicServerTlsStrategy.java
@@ -49,11 +49,16 @@ import org.apache.hc.core5.util.Timeout;
 public class BasicServerTlsStrategy implements TlsStrategy {
 
     private final SSLContext sslContext;
+    @SuppressWarnings("deprecation")
     private final SecurePortStrategy securePortStrategy;
     private final SSLBufferMode sslBufferMode;
     private final SSLSessionInitializer initializer;
     private final SSLSessionVerifier verifier;
 
+    /**
+     * @deprecated Use {@link BasicServerTlsStrategy#BasicServerTlsStrategy(SSLContext, SSLBufferMode, SSLSessionInitializer, SSLSessionVerifier)}
+     */
+    @Deprecated
     public BasicServerTlsStrategy(
             final SSLContext sslContext,
             final SecurePortStrategy securePortStrategy,
@@ -67,6 +72,10 @@ public class BasicServerTlsStrategy implements TlsStrategy {
         this.verifier = verifier;
     }
 
+    /**
+     * @deprecated Use {@link BasicServerTlsStrategy#BasicServerTlsStrategy(SSLContext, SSLSessionInitializer, SSLSessionVerifier)}
+     */
+    @Deprecated
     public BasicServerTlsStrategy(
             final SSLContext sslContext,
             final SecurePortStrategy securePortStrategy,
@@ -75,6 +84,10 @@ public class BasicServerTlsStrategy implements TlsStrategy {
         this(sslContext, securePortStrategy, null, initializer, verifier);
     }
 
+    /**
+     * @deprecated Use {@link BasicServerTlsStrategy#BasicServerTlsStrategy(SSLContext, SSLSessionVerifier)}
+     */
+    @Deprecated
     public BasicServerTlsStrategy(
             final SSLContext sslContext,
             final SecurePortStrategy securePortStrategy,
@@ -82,14 +95,59 @@ public class BasicServerTlsStrategy implements TlsStrategy {
         this(sslContext, securePortStrategy, null, null, verifier);
     }
 
+    /**
+     * @deprecated Use {@link BasicServerTlsStrategy#BasicServerTlsStrategy(SSLContext)}
+     */
+    @Deprecated
     public BasicServerTlsStrategy(final SSLContext sslContext, final SecurePortStrategy securePortStrategy) {
         this(sslContext, securePortStrategy, null, null, null);
     }
 
+    /**
+     * @deprecated Use {@link BasicServerTlsStrategy#BasicServerTlsStrategy()}
+     */
+    @Deprecated
     public BasicServerTlsStrategy(final SecurePortStrategy securePortStrategy) {
         this(SSLContexts.createSystemDefault(), securePortStrategy);
     }
 
+    public BasicServerTlsStrategy(
+            final SSLContext sslContext,
+            final SSLBufferMode sslBufferMode,
+            final SSLSessionInitializer initializer,
+            final SSLSessionVerifier verifier) {
+        this.sslContext = Args.notNull(sslContext, "SSL context");
+        this.sslBufferMode = sslBufferMode;
+        this.initializer = initializer;
+        this.verifier = verifier;
+        this.securePortStrategy = null;
+    }
+
+    public BasicServerTlsStrategy(
+            final SSLContext sslContext,
+            final SSLSessionInitializer initializer,
+            final SSLSessionVerifier verifier) {
+        this(sslContext, (SSLBufferMode) null, initializer, verifier);
+    }
+
+    public BasicServerTlsStrategy(
+            final SSLContext sslContext,
+            final SSLSessionVerifier verifier) {
+        this(sslContext, (SSLBufferMode) null, null, verifier);
+    }
+
+    public BasicServerTlsStrategy(final SSLContext sslContext) {
+        this(sslContext, null, null, null, null);
+    }
+
+    public BasicServerTlsStrategy() {
+        this(SSLContexts.createSystemDefault());
+    }
+
+    private boolean isApplicable(final SocketAddress localAddress) {
+        return securePortStrategy == null || securePortStrategy.isSecure(localAddress);
+    }
+
     @Override
     public boolean upgrade(
             final TransportSecurityLayer tlsSession,
@@ -98,7 +156,7 @@ public class BasicServerTlsStrategy implements TlsStrategy {
             final SocketAddress remoteAddress,
             final Object attachment,
             final Timeout handshakeTimeout) {
-        if (securePortStrategy != null && securePortStrategy.isSecure(localAddress)) {
+        if (isApplicable(localAddress)) {
             tlsSession.startTls(sslContext, host, sslBufferMode,
                     TlsSupport.enforceStrongSecurity(initializer), verifier, handshakeTimeout);
             return true;
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/FixedPortStrategy.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/FixedPortStrategy.java
index ea62106..7d45df2 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/FixedPortStrategy.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/FixedPortStrategy.java
@@ -36,7 +36,10 @@ import org.apache.hc.core5.util.Args;
  * Basic implementation of {@link SecurePortStrategy} with a fixed list of secure ports.
  *
  * @since 5.0
+ *
+ * @deprecated Use configuration parameters provided by connection listeners.
  */
+@Deprecated
 public final class FixedPortStrategy implements SecurePortStrategy {
 
     private final int[] securePorts;
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/SecurePortStrategy.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/SecurePortStrategy.java
index a06aa4b..b560275 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/SecurePortStrategy.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/SecurePortStrategy.java
@@ -33,7 +33,10 @@ import java.net.SocketAddress;
  * Side-side strategy to determine if local endpoint should be secured with TLS.
  *
  * @since 5.0
+ *
+ * @deprecated Use configuration parameters provided by connection listeners.
  */
+@Deprecated
 public interface SecurePortStrategy {
 
     /**


[httpcomponents-core] 14/18: HTTPCORE-645: Chunked request streams reuse buffers between requests

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 9dfd5f3f869de8ae9c526ef05a8c7516db260b70
Author: Carter Kozak <c4...@gmail.com>
AuthorDate: Wed Aug 12 17:17:49 2020 -0400

    HTTPCORE-645: Chunked request streams reuse buffers between requests
    
    This adds a lazily initialized reusable buffer to the connection
    which is reused for all chunked request entities to avoid expensive
    array allocations each time a chunked request entity is sent.
---
 .../hc/core5/http/impl/io/BHttpConnectionBase.java | 13 +++++++++--
 .../hc/core5/http/impl/io/ChunkedOutputStream.java | 26 ++++++++++++++++++----
 2 files changed, 33 insertions(+), 6 deletions(-)

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 10683a5..95168ad 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
@@ -72,6 +72,8 @@ class BHttpConnectionBase implements BHttpConnection {
     final SessionOutputBufferImpl outbuffer;
     final BasicHttpConnectionMetrics connMetrics;
     final AtomicReference<SocketHolder> socketHolderRef;
+    // Lazily initialized chunked request buffer provided to ChunkedOutputStream.
+    private byte[] chunkedRequestBuffer;
 
     volatile ProtocolVersion version;
     volatile EndpointDetails endpointDetails;
@@ -147,13 +149,20 @@ class BHttpConnectionBase implements BHttpConnection {
         if (len >= 0) {
             return new ContentLengthOutputStream(buffer, outputStream, len);
         } else if (len == ContentLengthStrategy.CHUNKED) {
-            final int chunkSizeHint = http1Config.getChunkSizeHint() >= 0 ? http1Config.getChunkSizeHint() : 2048;
-            return new ChunkedOutputStream(buffer, outputStream, chunkSizeHint, trailers);
+            return new ChunkedOutputStream(buffer, outputStream, getChunkedRequestBuffer(), trailers);
         } else {
             return new IdentityOutputStream(buffer, outputStream);
         }
     }
 
+    private byte[] getChunkedRequestBuffer() {
+        if (chunkedRequestBuffer == null) {
+            final int chunkSizeHint = this.http1Config.getChunkSizeHint();
+            chunkedRequestBuffer = new byte[chunkSizeHint > 0 ? chunkSizeHint : 2048];
+        }
+        return chunkedRequestBuffer;
+    }
+
     protected InputStream createContentInputStream(
             final long len,
             final SessionInputBuffer buffer,
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedOutputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedOutputStream.java
index 91a0790..b8ab5fd 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedOutputStream.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedOutputStream.java
@@ -69,25 +69,43 @@ public class ChunkedOutputStream extends OutputStream {
      *
      * @param buffer Session output buffer
      * @param outputStream Output stream
-     * @param chunkSizeHint minimal chunk size hint
+     * @param chunkCache Buffer used to aggregate smaller writes into chunks.
      * @param trailerSupplier Trailer supplier. May be {@code null}
      *
-     * @since 5.0
+     * @since 5.1
      */
     public ChunkedOutputStream(
             final SessionOutputBuffer buffer,
             final OutputStream outputStream,
-            final int chunkSizeHint,
+            final byte[] chunkCache,
             final Supplier<List<? extends Header>> trailerSupplier) {
         super();
         this.buffer = Args.notNull(buffer, "Session output buffer");
         this.outputStream = Args.notNull(outputStream, "Output stream");
-        this.cache = new byte[chunkSizeHint > 0 ? chunkSizeHint : 2048];
+        this.cache = Args.notNull(chunkCache, "Chunk cache");
         this.lineBuffer = new CharArrayBuffer(32);
         this.trailerSupplier = trailerSupplier;
     }
 
     /**
+     * Constructor taking an integer chunk size hint.
+     *
+     * @param buffer Session output buffer
+     * @param outputStream Output stream
+     * @param chunkSizeHint minimal chunk size hint
+     * @param trailerSupplier Trailer supplier. May be {@code null}
+     *
+     * @since 5.0
+     */
+    public ChunkedOutputStream(
+            final SessionOutputBuffer buffer,
+            final OutputStream outputStream,
+            final int chunkSizeHint,
+            final Supplier<List<? extends Header>> trailerSupplier) {
+        this(buffer, outputStream, new byte[chunkSizeHint > 0 ? chunkSizeHint : 2048], trailerSupplier);
+    }
+
+    /**
      * Constructor with no trailers.
      *
      * @param buffer Session output buffer


[httpcomponents-core] 05/18: HTTPCORE-628: do not encode blanks as + in URI query component

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit c5903208a32bb6fa0f0c580072b7ea1393e1cf46
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Apr 18 15:43:10 2020 +0200

    HTTPCORE-628: do not encode blanks as + in URI query component
---
 .../org/apache/hc/core5/net/URLEncodedUtils.java   | 22 +++++++++++++--------
 .../org/apache/hc/core5/net/TestURIBuilder.java    | 23 +++++++++++++++-------
 .../apache/hc/core5/net/TestURLEncodedUtils.java   | 12 +++++------
 3 files changed, 36 insertions(+), 21 deletions(-)

diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/URLEncodedUtils.java b/httpcore5/src/main/java/org/apache/hc/core5/net/URLEncodedUtils.java
index f9c999c..93376be 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/net/URLEncodedUtils.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/URLEncodedUtils.java
@@ -206,7 +206,7 @@ public class URLEncodedUtils {
     static void formatSegments(final StringBuilder buf, final Iterable<String> segments, final Charset charset) {
         for (final String segment : segments) {
             buf.append(PATH_SEPARATOR);
-            urlEncode(buf, segment, charset, PATHSAFE, false);
+            urlEncode(buf, segment, charset, PATHSAFE);
         }
     }
 
@@ -419,8 +419,7 @@ public class URLEncodedUtils {
             final StringBuilder buf,
             final String content,
             final Charset charset,
-            final BitSet safechars,
-            final boolean blankAsPlus) {
+            final BitSet safechars) {
         if (content == null) {
             return;
         }
@@ -429,8 +428,6 @@ public class URLEncodedUtils {
             final int b = bb.get() & 0xff;
             if (safechars.get(b)) {
                 buf.append((char) b);
-            } else if (blankAsPlus && b == ' ') {
-                buf.append('+');
             } else {
                 buf.append("%");
                 final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX));
@@ -485,15 +482,24 @@ public class URLEncodedUtils {
         if (content == null) {
             return;
         }
-        urlEncode(buf, content, charset != null ? charset : StandardCharsets.UTF_8, URLENCODER, true);
+        urlEncode(buf, content, charset != null ? charset : StandardCharsets.UTF_8, URLENCODER);
+    }
+
+    static String encodeFormFields(final String content, final Charset charset) {
+        if (content == null) {
+            return null;
+        }
+        final StringBuilder buf = new StringBuilder();
+        urlEncode(buf, content, charset != null ? charset : StandardCharsets.UTF_8, URLENCODER);
+        return buf.toString();
     }
 
     static void encUserInfo(final StringBuilder buf, final String content, final Charset charset) {
-        urlEncode(buf, content, charset != null ? charset : StandardCharsets.UTF_8, USERINFO, false);
+        urlEncode(buf, content, charset != null ? charset : StandardCharsets.UTF_8, USERINFO);
     }
 
     static void encUric(final StringBuilder buf, final String content, final Charset charset) {
-        urlEncode(buf, content, charset != null ? charset : StandardCharsets.UTF_8, URIC, false);
+        urlEncode(buf, content, charset != null ? charset : StandardCharsets.UTF_8, URIC);
     }
 
 }
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 aeabdcb..cf68126 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
@@ -28,7 +28,6 @@ package org.apache.hc.core5.net;
 
 import java.net.InetAddress;
 import java.net.URI;
-import java.net.URLEncoder;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
@@ -191,7 +190,7 @@ public class TestURIBuilder {
         final URIBuilder uribuilder = new URIBuilder(uri).setParameter("param", "some other stuff")
             .setParameter("blah", "blah");
         final URI result = uribuilder.build();
-        Assert.assertEquals(new URI("http://localhost:80/?param=some+other+stuff&blah=blah"), result);
+        Assert.assertEquals(new URI("http://localhost:80/?param=some%20other%20stuff&blah=blah"), result);
     }
 
     @Test
@@ -216,7 +215,7 @@ public class TestURIBuilder {
         final URIBuilder uribuilder = new URIBuilder(uri).addParameter("param", "1 + 1 = 2")
             .addParameter("param", "blah&blah");
         final URI result = uribuilder.build();
-        Assert.assertEquals(new URI("http://localhost:80/?param=stuff&param=1+%2B+1+%3D+2&" +
+        Assert.assertEquals(new URI("http://localhost:80/?param=stuff&param=1%20%2B%201%20%3D%202&" +
                 "param=blah%26blah"), result);
     }
 
@@ -227,13 +226,13 @@ public class TestURIBuilder {
             .addParameter("blah", "blah");
         final URI result = uribuilder.build();
         Assert.assertEquals(new URI("http://localhost:80/?param=stuff&blah&blah&" +
-                "param=some+other+stuff&blah=blah"), result);
+                "param=some%20other%20stuff&blah=blah"), result);
     }
 
     @Test
     public void testQueryEncoding() throws Exception {
         final URI uri1 = new URI("https://somehost.com/stuff?client_id=1234567890" +
-                "&redirect_uri=https%3A%2F%2Fsomehost.com%2Fblah+blah%2F");
+                "&redirect_uri=https%3A%2F%2Fsomehost.com%2Fblah%20blah%2F");
         final URI uri2 = new URIBuilder("https://somehost.com/stuff")
             .addParameter("client_id","1234567890")
             .addParameter("redirect_uri","https://somehost.com/blah blah/").build();
@@ -356,8 +355,8 @@ public class TestURIBuilder {
     }
 
     public void assertBuild(final Charset charset, final URI uri) throws Exception {
-        final String encodedData1 = URLEncoder.encode("\"1\u00aa position\"", charset.displayName());
-        final String encodedData2 = URLEncoder.encode("Jos\u00e9 Abra\u00e3o", charset.displayName());
+        final String encodedData1 = URLEncodedUtils.encodeFormFields("\"1\u00aa position\"", charset);
+        final String encodedData2 = URLEncodedUtils.encodeFormFields("Jos\u00e9 Abra\u00e3o", charset);
 
         final String uriExpected = String.format("https://somehost.com/stuff?parameter1=value1&parameter2=%s&parameter3=%s", encodedData1, encodedData2);
 
@@ -455,4 +454,14 @@ public class TestURIBuilder {
         Assert.assertThat(uriBuilder.isOpaque(), CoreMatchers.equalTo(uri.isOpaque()));
     }
 
+    @Test
+    public void testAddParameterEncodingEquivalence() throws Exception {
+        final URI uri = new URI("http", null, "localhost", 80, "/",
+                "param=stuff with spaces", null);
+        final URIBuilder uribuilder = new URIBuilder().setScheme("http").setHost("localhost").setPort(80).setPath("/").addParameter(
+                "param", "stuff with spaces");
+        final URI result = uribuilder.build();
+        Assert.assertEquals(uri, result);
+    }
+
 }
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestURLEncodedUtils.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestURLEncodedUtils.java
index 047cda3..e7687a4 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestURLEncodedUtils.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestURLEncodedUtils.java
@@ -302,7 +302,7 @@ public class TestURLEncodedUtils {
 
         params.clear();
         params.add(new BasicNameValuePair("Name4", "Value 4&"));
-        Assert.assertEquals("Name4=Value+4%26", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
+        Assert.assertEquals("Name4=Value%204%26", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
 
         params.clear();
         params.add(new BasicNameValuePair("Name4", "Value+4&"));
@@ -310,7 +310,7 @@ public class TestURLEncodedUtils {
 
         params.clear();
         params.add(new BasicNameValuePair("Name4", "Value 4& =4"));
-        Assert.assertEquals("Name4=Value+4%26+%3D4", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
+        Assert.assertEquals("Name4=Value%204%26%20%3D4", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
 
         params.clear();
         params.add(new BasicNameValuePair("Name5", "aaa"));
@@ -327,7 +327,7 @@ public class TestURLEncodedUtils {
 
         params.clear();
         params.add(new BasicNameValuePair("Name8", "xx,  yy  ,zz"));
-        Assert.assertEquals("Name8=xx%2C++yy++%2Czz", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
+        Assert.assertEquals("Name8=xx%2C%20%20yy%20%20%2Czz", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
     }
 
     @Test
@@ -349,7 +349,7 @@ public class TestURLEncodedUtils {
 
         params.clear();
         params.add(new BasicNameValuePair("Name4", "Value 4&"));
-        Assert.assertEquals("Name4=Value+4%26", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
+        Assert.assertEquals("Name4=Value%204%26", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
 
         params.clear();
         params.add(new BasicNameValuePair("Name4", "Value+4&"));
@@ -357,7 +357,7 @@ public class TestURLEncodedUtils {
 
         params.clear();
         params.add(new BasicNameValuePair("Name4", "Value 4& =4"));
-        Assert.assertEquals("Name4=Value+4%26+%3D4", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
+        Assert.assertEquals("Name4=Value%204%26%20%3D4", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
 
         params.clear();
         params.add(new BasicNameValuePair("Name5", "aaa"));
@@ -372,7 +372,7 @@ public class TestURLEncodedUtils {
 
         params.clear();
         params.add(new BasicNameValuePair("Name8", "xx,  yy  ,zz"));
-        Assert.assertEquals("Name8=xx%2C++yy++%2Czz", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
+        Assert.assertEquals("Name8=xx%2C%20%20yy%20%20%2Czz", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
     }
 
     private List <NameValuePair> parse (final String params) {


[httpcomponents-core] 12/18: HTTPCORE-643: Implement NullEntity for convenience (#209)

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit ba87dac188f01a7259f8936f4c4eefebeac94c2d
Author: Carter Kozak <ck...@apache.org>
AuthorDate: Mon Aug 3 13:35:42 2020 -0400

    HTTPCORE-643: Implement NullEntity for convenience (#209)
---
 .../hc/core5/http/impl/io/BHttpConnectionBase.java |   1 +
 .../hc/core5/http/impl/io/EmptyInputStream.java    |   2 +
 .../hc/core5/http/impl/io/IncomingHttpEntity.java  |   1 +
 .../hc/core5/http/io/entity/BasicHttpEntity.java   |   1 -
 .../{impl/io => io/entity}/EmptyInputStream.java   |   4 +-
 .../entity/NullEntity.java}                        | 104 ++++++++-------------
 .../core5/http/io/entity/TestBasicHttpEntity.java  |   1 -
 .../http/io/entity/TestInputStreamEntity.java      |   1 -
 .../hc/core5/http/io/entity/TestNullEntity.java    |  90 ++++++++++++++++++
 .../http/protocol/TestStandardInterceptors.java    |   2 +-
 10 files changed, 134 insertions(+), 73 deletions(-)

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 4d8a80c..10683a5 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
@@ -58,6 +58,7 @@ import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics;
 import org.apache.hc.core5.http.io.BHttpConnection;
 import org.apache.hc.core5.http.io.SessionInputBuffer;
 import org.apache.hc.core5.http.io.SessionOutputBuffer;
+import org.apache.hc.core5.http.io.entity.EmptyInputStream;
 import org.apache.hc.core5.io.CloseMode;
 import org.apache.hc.core5.io.Closer;
 import org.apache.hc.core5.net.InetAddressUtils;
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 8508245..1c3d2e4 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
@@ -31,7 +31,9 @@ import java.io.InputStream;
 
 /**
  * @since 4.4
+ * @deprecated Please use {@link org.apache.hc.core5.http.io.entity.EmptyInputStream}
  */
+@Deprecated
 public final class EmptyInputStream extends InputStream {
 
     public static final EmptyInputStream INSTANCE = new EmptyInputStream();
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 e831325..ee1228f 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,6 +38,7 @@ import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
+import org.apache.hc.core5.http.io.entity.EmptyInputStream;
 import org.apache.hc.core5.io.Closer;
 
 class IncomingHttpEntity implements HttpEntity {
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 1d9407e..730ac91 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
@@ -33,7 +33,6 @@ import java.io.InputStream;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.ContentType;
-import org.apache.hc.core5.http.impl.io.EmptyInputStream;
 import org.apache.hc.core5.io.Closer;
 import org.apache.hc.core5.util.Args;
 
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/io/entity/EmptyInputStream.java
similarity index 97%
copy from httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/EmptyInputStream.java
copy to httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EmptyInputStream.java
index 8508245..294952b 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/io/entity/EmptyInputStream.java
@@ -25,12 +25,12 @@
  *
  */
 
-package org.apache.hc.core5.http.impl.io;
+package org.apache.hc.core5.http.io.entity;
 
 import java.io.InputStream;
 
 /**
- * @since 4.4
+ * @since 5.1
  */
 public final class EmptyInputStream extends 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/io/entity/NullEntity.java
similarity index 53%
copy from httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/IncomingHttpEntity.java
copy to httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/NullEntity.java
index e831325..b4b1805 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/io/entity/NullEntity.java
@@ -25,7 +25,13 @@
  *
  */
 
-package org.apache.hc.core5.http.impl.io;
+package org.apache.hc.core5.http.io.entity;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.function.Supplier;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpEntity;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -34,103 +40,67 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
-import org.apache.hc.core5.function.Supplier;
-import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HttpEntity;
-import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
-import org.apache.hc.core5.io.Closer;
-
-class IncomingHttpEntity implements HttpEntity {
-
-    private final InputStream content;
-    private final long len;
-    private final boolean chunked;
-    private final Header contentType;
-    private final Header contentEncoding;
-
-    IncomingHttpEntity(final InputStream content, final long len, final boolean chunked, final Header contentType, final Header contentEncoding) {
-        this.content = content;
-        this.len = len;
-        this.chunked = chunked;
-        this.contentType = contentType;
-        this.contentEncoding = contentEncoding;
-    }
+/**
+ * An empty entity with no content-type. This type may be used for convenience
+ * in place of an empty {@link ByteArrayEntity}.
+ *
+ * @since 5.1
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public final class NullEntity implements HttpEntity {
 
-    @Override
-    public boolean isRepeatable() {
-        return false;
-    }
+    public static final NullEntity INSTANCE = new NullEntity();
+
+    private NullEntity() {}
 
     @Override
-    public boolean isChunked() {
-        return chunked;
+    public boolean isRepeatable() {
+        return true;
     }
 
     @Override
-    public long getContentLength() {
-        return len;
+    public InputStream getContent() throws IOException, UnsupportedOperationException {
+        return EmptyInputStream.INSTANCE;
     }
 
     @Override
-    public String getContentType() {
-        return contentType != null ? contentType.getValue() : null;
-    }
+    public void writeTo(final OutputStream outStream) throws IOException {}
 
     @Override
-    public String getContentEncoding() {
-        return contentEncoding != null ? contentEncoding.getValue() : null;
+    public boolean isStreaming() {
+        return false;
     }
 
     @Override
-    public InputStream getContent() throws IOException, IllegalStateException {
-        return content;
+    public Supplier<List<? extends Header>> getTrailers() {
+        return null;
     }
 
     @Override
-    public boolean isStreaming() {
-        return content != null && content != EmptyInputStream.INSTANCE;
-    }
+    public void close() throws IOException {}
 
     @Override
-    public void writeTo(final OutputStream outStream) throws IOException {
-        AbstractHttpEntity.writeTo(this, outStream);
+    public long getContentLength() {
+        return 0;
     }
 
     @Override
-    public Supplier<List<? extends Header>> getTrailers() {
+    public String getContentType() {
         return null;
     }
 
     @Override
-    public Set<String> getTrailerNames() {
-        return Collections.emptySet();
+    public String getContentEncoding() {
+        return null;
     }
 
     @Override
-    public void close() throws IOException {
-        Closer.close(content);
+    public boolean isChunked() {
+        return false;
     }
 
     @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append('[');
-        sb.append("Content-Type: ");
-        sb.append(getContentType());
-        sb.append(',');
-        sb.append("Content-Encoding: ");
-        sb.append(getContentEncoding());
-        sb.append(',');
-        final long len = getContentLength();
-        if (len >= 0) {
-            sb.append("Content-Length: ");
-            sb.append(len);
-            sb.append(',');
-        }
-        sb.append("Chunked: ");
-        sb.append(isChunked());
-        sb.append(']');
-        return sb.toString();
+    public Set<String> getTrailerNames() {
+        return Collections.emptySet();
     }
-
 }
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 b2ac16e..8012585 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
@@ -32,7 +32,6 @@ import java.io.ByteArrayOutputStream;
 import java.nio.charset.StandardCharsets;
 
 import org.apache.hc.core5.http.ContentType;
-import org.apache.hc.core5.http.impl.io.EmptyInputStream;
 import org.junit.Assert;
 import org.junit.Test;
 
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 04f937b..35e0e17 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
@@ -33,7 +33,6 @@ import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 
 import org.apache.hc.core5.http.ContentType;
-import org.apache.hc.core5.http.impl.io.EmptyInputStream;
 import org.junit.Assert;
 import org.junit.Test;
 
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
new file mode 100644
index 0000000..c2abce3
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestNullEntity.java
@@ -0,0 +1,90 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.http.io.entity;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+
+public class TestNullEntity {
+
+    @Test
+    public void testLength() {
+        assertEquals(0, NullEntity.INSTANCE.getContentLength());
+    }
+
+    @Test
+    public void testContentType() {
+        assertNull(NullEntity.INSTANCE.getContentType());
+    }
+
+    @Test
+    public void testContentEncoding() {
+        assertNull(NullEntity.INSTANCE.getContentEncoding());
+    }
+
+    @Test
+    public void testTrailerNames() {
+        assertEquals(Collections.emptySet(), NullEntity.INSTANCE.getTrailerNames());
+    }
+
+    @Test
+    public void testContentStream() throws IOException {
+        try (InputStream content = NullEntity.INSTANCE.getContent()) {
+            assertEquals(-1, content.read());
+        }
+        // Closing the resource should have no impact
+        try (InputStream content = NullEntity.INSTANCE.getContent()) {
+            assertEquals(-1, content.read());
+        }
+    }
+
+    @Test
+    public void testWriteTo() throws IOException {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        NullEntity.INSTANCE.writeTo(baos);
+        assertEquals(0, baos.size());
+    }
+
+    @Test
+    public void testIsStreaming() {
+        assertFalse(NullEntity.INSTANCE.isStreaming());
+    }
+
+    @Test
+    public void testIsChunked() {
+        assertFalse(NullEntity.INSTANCE.isChunked());
+    }
+}
\ No newline at end of file
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 8ed0f1e..cef1f8a 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
@@ -38,7 +38,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.impl.io.EmptyInputStream;
+import org.apache.hc.core5.http.io.entity.EmptyInputStream;
 import org.apache.hc.core5.http.io.entity.BasicHttpEntity;
 import org.apache.hc.core5.http.io.entity.HttpEntities;
 import org.apache.hc.core5.http.io.entity.StringEntity;


[httpcomponents-core] 16/18: Use decimal numbers for endpoint/execution IDs

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 19dea0fc10e920e4f7e2a8884a3e5319818951fe
Author: Michael Osipov <mi...@apache.org>
AuthorDate: Tue Aug 18 12:19:02 2020 +0200

    Use decimal numbers for endpoint/execution IDs
    
    This closes #219
---
 httpcore5/src/main/java/org/apache/hc/core5/reactor/IOSessionImpl.java  | 2 +-
 .../org/apache/hc/core5/http/examples/AsyncReverseProxyExample.java     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

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 3cffb58..e7e1e7e 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
@@ -71,7 +71,7 @@ class IOSessionImpl implements IOSession {
         this.commandQueue = new ConcurrentLinkedDeque<>();
         this.lock = new ReentrantLock();
         this.socketTimeout = Timeout.DISABLED;
-        this.id = String.format(type + "-%08X", COUNT.getAndIncrement());
+        this.id = String.format(type + "-%010d", COUNT.getAndIncrement());
         this.handlerRef = new AtomicReference<>();
         this.status = new AtomicReference<>(Status.ACTIVE);
         final long currentTimeMillis = System.currentTimeMillis();
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 8136cab..5771d97 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
@@ -251,7 +251,7 @@ public class AsyncReverseProxyExample {
         AsyncClientEndpoint clientEndpoint;
 
         ProxyExchangeState() {
-            this.id = String.format("%08X", COUNT.getAndIncrement());
+            this.id = String.format("%010d", COUNT.getAndIncrement());
         }
 
     }


[httpcomponents-core] 10/18: RFC 3986 conformance: revised URI parsing and formatting; URLEncodedUtils deprecated in favor of WWWFormCodec

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 9885d2c74a651016d9f28dd8b6e93409f2820f14
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Mon Jul 20 17:30:22 2020 +0200

    RFC 3986 conformance: revised URI parsing and formatting; URLEncodedUtils deprecated in favor of WWWFormCodec
---
 .../hc/core5/http2/examples/H2GreetingServer.java  |   4 +-
 .../hc/core5/testing/framework/FrameworkTest.java  |   5 +-
 .../framework/TestingFrameworkRequestHandler.java  |   7 +-
 .../hc/core5/http/io/entity/EntityUtils.java       |   4 +-
 .../hc/core5/http/io/entity/HttpEntities.java      |   4 +-
 .../http/nio/entity/AsyncEntityProducers.java      |   4 +-
 .../http/nio/support/AsyncRequestBuilder.java      |   4 +-
 .../java/org/apache/hc/core5/net/PercentCodec.java | 163 +++++++++
 .../java/org/apache/hc/core5/net/URIBuilder.java   | 233 +++++++++---
 .../org/apache/hc/core5/net/URLEncodedUtils.java   | 303 ++--------------
 .../java/org/apache/hc/core5/net/WWWFormCodec.java |  83 +++++
 .../hc/core5/http/NameValuePairListMatcher.java    |  85 +++++
 .../hc/core5/http/io/entity/TestEntityUtils.java   |   4 +-
 .../org/apache/hc/core5/net/TestPercentCodec.java  |  70 ++++
 .../org/apache/hc/core5/net/TestURIBuilder.java    | 206 ++++++++---
 .../apache/hc/core5/net/TestURLEncodedUtils.java   | 394 ---------------------
 .../org/apache/hc/core5/net/TestWWWFormCodec.java  | 123 +++++++
 17 files changed, 913 insertions(+), 783 deletions(-)

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 b1dc3e1..551be6f 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
@@ -63,7 +63,7 @@ import org.apache.hc.core5.http2.HttpVersionPolicy;
 import org.apache.hc.core5.http2.config.H2Config;
 import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap;
 import org.apache.hc.core5.io.CloseMode;
-import org.apache.hc.core5.net.URLEncodedUtils;
+import org.apache.hc.core5.net.WWWFormCodec;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.ListenerEndpoint;
 import org.apache.hc.core5.util.TimeValue;
@@ -174,7 +174,7 @@ public class H2GreetingServer {
             if (contentType != null && contentType.isSameMimeType(ContentType.APPLICATION_FORM_URLENCODED)) {
 
                 // decoding the form entity into key/value pairs:
-                final List<NameValuePair> args = URLEncodedUtils.parse(httpEntity, contentType.getCharset());
+                final List<NameValuePair> args = WWWFormCodec.parse(httpEntity, contentType.getCharset());
                 if (!args.isEmpty()) {
                     name = args.get(0).getValue();
                 }
diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/FrameworkTest.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/FrameworkTest.java
index 326a8f1..9685487 100644
--- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/FrameworkTest.java
+++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/FrameworkTest.java
@@ -46,7 +46,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.hc.core5.http.NameValuePair;
-import org.apache.hc.core5.net.URLEncodedUtils;
+import org.apache.hc.core5.net.URIBuilder;
 
 public class FrameworkTest {
 
@@ -112,7 +112,8 @@ public class FrameworkTest {
             if (path != null) {
                 final URI uri = path.startsWith("/") ? new URI("http://localhost:8080" + path) :
                                                  new URI("http://localhost:8080/");
-                final List<NameValuePair> params = URLEncodedUtils.parse(uri, StandardCharsets.UTF_8);
+                final URIBuilder uriBuilder = new URIBuilder(uri, StandardCharsets.UTF_8);
+                final List<NameValuePair> params = uriBuilder.getQueryParams();
                 @SuppressWarnings("unchecked")
                 final Map<String, Object> queryMap = (Map<String, Object>) request.get(QUERY);
                 for (final NameValuePair param : params) {
diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/TestingFrameworkRequestHandler.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/TestingFrameworkRequestHandler.java
index 07bd7d5..e757b62 100644
--- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/TestingFrameworkRequestHandler.java
+++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/TestingFrameworkRequestHandler.java
@@ -45,17 +45,17 @@ import java.util.Map.Entry;
 
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.http.io.HttpRequestHandler;
-import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.io.entity.EntityUtils;
 import org.apache.hc.core5.http.io.entity.StringEntity;
 import org.apache.hc.core5.http.protocol.HttpContext;
-import org.apache.hc.core5.net.URLEncodedUtils;
+import org.apache.hc.core5.net.URIBuilder;
 
 public class TestingFrameworkRequestHandler implements HttpRequestHandler {
     protected Throwable thrown;
@@ -137,7 +137,8 @@ public class TestingFrameworkRequestHandler implements HttpRequestHandler {
             final Map<String, String> expectedQuery = (Map<String, String>) requestExpectations.get(QUERY);
             if (expectedQuery != null) {
                 final URI uri = request.getUri();
-                final List<NameValuePair> actualParams = URLEncodedUtils.parse(uri, StandardCharsets.UTF_8);
+                final URIBuilder uriBuilder = new URIBuilder(uri, StandardCharsets.UTF_8);
+                final List<NameValuePair> actualParams = uriBuilder.getQueryParams();
                 final Map<String, String> actualParamsMap = new HashMap<>();
                 for (final NameValuePair actualParam : actualParams) {
                     actualParamsMap.put(actualParam.getName(), actualParam.getValue());
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 0e6d70a..f4b44a3 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
@@ -45,7 +45,7 @@ import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.http.ParseException;
 import org.apache.hc.core5.io.Closer;
-import org.apache.hc.core5.net.URLEncodedUtils;
+import org.apache.hc.core5.net.WWWFormCodec;
 import org.apache.hc.core5.util.Args;
 import org.apache.hc.core5.util.ByteArrayBuffer;
 import org.apache.hc.core5.util.CharArrayBuffer;
@@ -412,7 +412,7 @@ public final class EntityUtils {
         if (buf.isEmpty()) {
             return Collections.emptyList();
         }
-        return URLEncodedUtils.parse(buf, charset, '&');
+        return WWWFormCodec.parse(buf, charset);
     }
 
 }
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 597e1ea..6331733 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
@@ -46,7 +46,7 @@ import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.io.IOCallback;
-import org.apache.hc.core5.net.URLEncodedUtils;
+import org.apache.hc.core5.net.WWWFormCodec;
 import org.apache.hc.core5.util.Args;
 
 /**
@@ -88,7 +88,7 @@ public final class HttpEntities {
         final ContentType contentType = charset != null ?
                 ContentType.APPLICATION_FORM_URLENCODED.withCharset(charset) :
                 ContentType.APPLICATION_FORM_URLENCODED;
-        return create(URLEncodedUtils.format(parameters, contentType.getCharset()), contentType);
+        return create(WWWFormCodec.format(parameters, contentType.getCharset()), contentType);
     }
 
     public static HttpEntity create(final IOCallback<OutputStream> callback, final ContentType contentType) {
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 3fd20d1..b34438d 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
@@ -45,7 +45,7 @@ import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.http.nio.AsyncEntityProducer;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
 import org.apache.hc.core5.http.nio.StreamChannel;
-import org.apache.hc.core5.net.URLEncodedUtils;
+import org.apache.hc.core5.net.WWWFormCodec;
 
 /**
  * {AsyncEntityProducer} factory methods.
@@ -82,7 +82,7 @@ public final class AsyncEntityProducers {
         final ContentType contentType = charset != null ?
                 ContentType.APPLICATION_FORM_URLENCODED.withCharset(charset) :
                 ContentType.APPLICATION_FORM_URLENCODED;
-        return create(URLEncodedUtils.format(parameters, contentType.getCharset()), contentType);
+        return create(WWWFormCodec.format(parameters, contentType.getCharset()), contentType);
     }
 
     public static AsyncEntityProducer createBinary(
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncRequestBuilder.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncRequestBuilder.java
index 81149b2..720980e 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncRequestBuilder.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncRequestBuilder.java
@@ -50,7 +50,7 @@ import org.apache.hc.core5.http.nio.AsyncRequestProducer;
 import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityProducer;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
 import org.apache.hc.core5.net.URIBuilder;
-import org.apache.hc.core5.net.URLEncodedUtils;
+import org.apache.hc.core5.net.WWWFormCodec;
 import org.apache.hc.core5.util.Args;
 
 /**
@@ -368,7 +368,7 @@ public class AsyncRequestBuilder {
         AsyncEntityProducer entityProducerCopy = entityProducer;
         if (parameters != null && !parameters.isEmpty()) {
             if (entityProducerCopy == null && (Method.POST.isSame(method) || Method.PUT.isSame(method))) {
-                final String content = URLEncodedUtils.format(
+                final String content = WWWFormCodec.format(
                         parameters,
                         charset != null ? charset : ContentType.APPLICATION_FORM_URLENCODED.getCharset());
                 entityProducerCopy = new StringAsyncEntityProducer(
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
new file mode 100644
index 0000000..2782282
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/PercentCodec.java
@@ -0,0 +1,163 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.net;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.BitSet;
+
+/**
+ * Percent-encoding mechanism defined in RFC 3986
+ *
+ * @since 5.1
+ */
+public class PercentCodec {
+
+    static final BitSet GEN_DELIMS = new BitSet(256);
+    static final BitSet SUB_DELIMS = new BitSet(256);
+    static final BitSet UNRESERVED = new BitSet(256);
+    static final BitSet URIC = new BitSet(256);
+
+    static {
+        GEN_DELIMS.set(':');
+        GEN_DELIMS.set('/');
+        GEN_DELIMS.set('?');
+        GEN_DELIMS.set('#');
+        GEN_DELIMS.set('[');
+        GEN_DELIMS.set(']');
+        GEN_DELIMS.set('@');
+
+        SUB_DELIMS.set('!');
+        SUB_DELIMS.set('$');
+        SUB_DELIMS.set('&');
+        SUB_DELIMS.set('\'');
+        SUB_DELIMS.set('(');
+        SUB_DELIMS.set(')');
+        SUB_DELIMS.set('*');
+        SUB_DELIMS.set('+');
+        SUB_DELIMS.set(',');
+        SUB_DELIMS.set(';');
+        SUB_DELIMS.set('=');
+
+        for (int i = 'a'; i <= 'z'; i++) {
+            UNRESERVED.set(i);
+        }
+        for (int i = 'A'; i <= 'Z'; i++) {
+            UNRESERVED.set(i);
+        }
+        // numeric characters
+        for (int i = '0'; i <= '9'; i++) {
+            UNRESERVED.set(i);
+        }
+        UNRESERVED.set('-');
+        UNRESERVED.set('.');
+        UNRESERVED.set('_');
+        UNRESERVED.set('~');
+        URIC.or(SUB_DELIMS);
+        URIC.or(UNRESERVED);
+    }
+
+    private static final int RADIX = 16;
+
+    static void encode(final StringBuilder buf, final CharSequence content, final Charset charset,
+                       final BitSet safechars, final boolean blankAsPlus) {
+        if (content == null) {
+            return;
+        }
+        final CharBuffer cb = CharBuffer.wrap(content);
+        final ByteBuffer bb = (charset != null ? charset : StandardCharsets.UTF_8).encode(cb);
+        while (bb.hasRemaining()) {
+            final int b = bb.get() & 0xff;
+            if (safechars.get(b)) {
+                buf.append((char) b);
+            } else if (blankAsPlus && b == ' ') {
+                buf.append("+");
+            } else {
+                buf.append("%");
+                final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX));
+                final char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, RADIX));
+                buf.append(hex1);
+                buf.append(hex2);
+            }
+        }
+    }
+
+    static void encode(final StringBuilder buf, final CharSequence content, final Charset charset, final boolean blankAsPlus) {
+        encode(buf, content, charset, UNRESERVED, blankAsPlus);
+    }
+
+    public static void encode(final StringBuilder buf, final CharSequence content, final Charset charset) {
+        encode(buf, content, charset, UNRESERVED, false);
+    }
+
+    public static String encode(final CharSequence content, final Charset charset) {
+        if (content == null) {
+            return null;
+        }
+        final StringBuilder buf = new StringBuilder();
+        encode(buf, content, charset, UNRESERVED, false);
+        return buf.toString();
+    }
+
+    static String decode(final CharSequence content, final Charset charset, final boolean plusAsBlank) {
+        if (content == null) {
+            return null;
+        }
+        final ByteBuffer bb = ByteBuffer.allocate(content.length());
+        final CharBuffer cb = CharBuffer.wrap(content);
+        while (cb.hasRemaining()) {
+            final char c = cb.get();
+            if (c == '%' && cb.remaining() >= 2) {
+                final char uc = cb.get();
+                final char lc = cb.get();
+                final int u = Character.digit(uc, RADIX);
+                final int l = Character.digit(lc, RADIX);
+                if (u != -1 && l != -1) {
+                    bb.put((byte) ((u << 4) + l));
+                } else {
+                    bb.put((byte) '%');
+                    bb.put((byte) uc);
+                    bb.put((byte) lc);
+                }
+            } else if (plusAsBlank && c == '+') {
+                bb.put((byte) ' ');
+            } else {
+                bb.put((byte) c);
+            }
+        }
+        bb.flip();
+        return (charset != null ? charset : StandardCharsets.UTF_8).decode(bb).toString();
+    }
+
+    public static String decode(final CharSequence content, final Charset charset) {
+        return decode(content, charset, false);
+    }
+
+}
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 3fbe2d7..70358e2 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,6 +34,7 @@ import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -41,7 +42,9 @@ import java.util.List;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.apache.hc.core5.http.message.ParserCursor;
 import org.apache.hc.core5.util.TextUtils;
+import org.apache.hc.core5.util.Tokenizer;
 
 /**
  * Builder for {@link URI} instances.
@@ -75,6 +78,7 @@ public class URIBuilder {
     private String host;
     private int port;
     private String encodedPath;
+    private boolean pathRootless;
     private List<String> pathSegments;
     private String encodedQuery;
     private List<NameValuePair> queryParams;
@@ -98,7 +102,7 @@ public class URIBuilder {
      * @throws URISyntaxException if the input is not a valid URI
      */
     public URIBuilder(final String string) throws URISyntaxException {
-        this(new URI(string), null);
+        this(new URI(string), StandardCharsets.UTF_8);
     }
 
     /**
@@ -106,7 +110,7 @@ public class URIBuilder {
      * @param uri
      */
     public URIBuilder(final URI uri) {
-        this(uri, null);
+        this(uri, StandardCharsets.UTF_8);
     }
 
     /**
@@ -125,8 +129,7 @@ public class URIBuilder {
      */
     public URIBuilder(final URI uri, final Charset charset) {
         super();
-        setCharset(charset);
-        digestURI(uri);
+        digestURI(uri, charset);
     }
 
     public URIBuilder setCharset(final Charset charset) {
@@ -138,18 +141,118 @@ public class URIBuilder {
         return charset;
     }
 
-    private List <NameValuePair> parseQuery(final String query, final Charset charset) {
-        if (query != null && !query.isEmpty()) {
-            return URLEncodedUtils.parse(query, charset);
+    private static final char QUERY_PARAM_SEPARATOR = '&';
+    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);
+    }
+
+    static List<NameValuePair> parseQuery(final CharSequence s, final Charset charset, final boolean plusAsBlank) {
+        if (s == null) {
+            return null;
+        }
+        final Tokenizer tokenParser = Tokenizer.INSTANCE;
+        final ParserCursor cursor = new ParserCursor(0, s.length());
+        final List<NameValuePair> list = new ArrayList<>();
+        while (!cursor.atEnd()) {
+            final String name = tokenParser.parseToken(s, cursor, QUERY_PARAM_SEPARATORS);
+            String value = null;
+            if (!cursor.atEnd()) {
+                final int delim = s.charAt(cursor.getPos());
+                cursor.updatePos(cursor.getPos() + 1);
+                if (delim == PARAM_VALUE_SEPARATOR) {
+                    value = tokenParser.parseToken(s, cursor, QUERY_VALUE_SEPARATORS);
+                    if (!cursor.atEnd()) {
+                        cursor.updatePos(cursor.getPos() + 1);
+                    }
+                }
+            }
+            if (!name.isEmpty()) {
+                list.add(new BasicNameValuePair(
+                        PercentCodec.decode(name, charset, plusAsBlank),
+                        PercentCodec.decode(value, charset, plusAsBlank)));
+            }
         }
-        return null;
+        return list;
     }
 
-    private List <String> parsePath(final String path, final Charset charset) {
-        if (path != null && !path.isEmpty()) {
-            return URLEncodedUtils.parsePathSegments(path, charset);
+    static List<String> splitPath(final CharSequence s) {
+        if (s == null) {
+            return null;
+        }
+        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()))) {
+            cursor.updatePos(cursor.getPos() + 1);
+        }
+        final List<String> list = new ArrayList<>();
+        final StringBuilder buf = new StringBuilder();
+        for (;;) {
+            if (cursor.atEnd()) {
+                list.add(buf.toString());
+                break;
+            }
+            final char current = s.charAt(cursor.getPos());
+            if (PATH_SEPARATORS.get(current)) {
+                list.add(buf.toString());
+                buf.setLength(0);
+            } else {
+                buf.append(current);
+            }
+            cursor.updatePos(cursor.getPos() + 1);
+        }
+        return list;
+    }
+
+    static List<String> parsePath(final CharSequence s, final Charset charset) {
+        if (s == null) {
+            return null;
+        }
+        final List<String> segments = splitPath(s);
+        final List<String> list = new ArrayList<>(segments.size());
+        for (final String segment: segments) {
+            list.add(PercentCodec.decode(segment, charset));
+        }
+        return list;
+    }
+
+    static void formatPath(final StringBuilder buf, final Iterable<String> segments, final boolean rootless, final Charset charset) {
+        int i = 0;
+        for (final String segment : segments) {
+            if (i > 0 || !rootless) {
+                buf.append(PATH_SEPARATOR);
+            }
+            PercentCodec.encode(buf, segment, charset);
+            i++;
+        }
+    }
+
+    static void formatQuery(final StringBuilder buf, final Iterable<? extends NameValuePair> params, final Charset charset,
+                            final boolean blankAsPlus) {
+        int i = 0;
+        for (final NameValuePair parameter : params) {
+            if (i > 0) {
+                buf.append(QUERY_PARAM_SEPARATOR);
+            }
+            PercentCodec.encode(buf, parameter.getName(), charset, blankAsPlus);
+            if (parameter.getValue() != null) {
+                buf.append(PARAM_VALUE_SEPARATOR);
+                PercentCodec.encode(buf, parameter.getValue(), charset, blankAsPlus);
+            }
+            i++;
         }
-        return null;
     }
 
     /**
@@ -167,14 +270,23 @@ public class URIBuilder {
         if (this.encodedSchemeSpecificPart != null) {
             sb.append(this.encodedSchemeSpecificPart);
         } else {
+            final boolean authoritySpecified;
             if (this.encodedAuthority != null) {
                 sb.append("//").append(this.encodedAuthority);
+                authoritySpecified = true;
             } else if (this.host != null) {
                 sb.append("//");
                 if (this.encodedUserInfo != null) {
                     sb.append(this.encodedUserInfo).append("@");
                 } else if (this.userInfo != null) {
-                    encodeUserInfo(sb, this.userInfo);
+                    final int idx = this.userInfo.indexOf(':');
+                    if (idx != -1) {
+                        PercentCodec.encode(sb, this.userInfo.substring(0, idx), this.charset);
+                        sb.append(':');
+                        PercentCodec.encode(sb, this.userInfo.substring(idx + 1), this.charset);
+                    } else {
+                        PercentCodec.encode(sb, this.userInfo, this.charset);
+                    }
                     sb.append("@");
                 }
                 if (InetAddressUtils.isIPv6Address(this.host)) {
@@ -185,43 +297,38 @@ public class URIBuilder {
                 if (this.port >= 0) {
                     sb.append(":").append(this.port);
                 }
+                authoritySpecified = true;
+            } else {
+                authoritySpecified = false;
             }
             if (this.encodedPath != null) {
-                sb.append(normalizePath(this.encodedPath, sb.length() == 0));
+                if (authoritySpecified && !TextUtils.isEmpty(this.encodedPath) && !this.encodedPath.startsWith("/")) {
+                    sb.append('/');
+                }
+                sb.append(this.encodedPath);
             } else if (this.pathSegments != null) {
-                encodePath(sb, this.pathSegments);
+                formatPath(sb, this.pathSegments, !authoritySpecified && this.pathRootless, this.charset);
             }
             if (this.encodedQuery != null) {
                 sb.append("?").append(this.encodedQuery);
             } else if (this.queryParams != null && !this.queryParams.isEmpty()) {
                 sb.append("?");
-                encodeUrlForm(sb, this.queryParams);
+                formatQuery(sb, this.queryParams, this.charset, false);
             } else if (this.query != null) {
                 sb.append("?");
-                encodeUric(sb, this.query);
+                PercentCodec.encode(sb, this.query, this.charset, PercentCodec.URIC, false);
             }
         }
         if (this.encodedFragment != null) {
             sb.append("#").append(this.encodedFragment);
         } else if (this.fragment != null) {
             sb.append("#");
-            encodeUric(sb, this.fragment);
+            PercentCodec.encode(sb, this.fragment, this.charset);
         }
         return sb.toString();
     }
 
-    private static String normalizePath(final String path, final boolean relative) {
-        String s = path;
-        if (TextUtils.isBlank(s)) {
-            return "";
-        }
-        if (!relative && !s.startsWith("/")) {
-            s = "/" + s;
-        }
-        return s;
-    }
-
-    private void digestURI(final URI uri) {
+    private void digestURI(final URI uri, final Charset charset) {
         this.scheme = uri.getScheme();
         this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart();
         this.encodedAuthority = uri.getRawAuthority();
@@ -230,27 +337,13 @@ public class URIBuilder {
         this.encodedUserInfo = uri.getRawUserInfo();
         this.userInfo = uri.getUserInfo();
         this.encodedPath = uri.getRawPath();
-        this.pathSegments = parsePath(uri.getRawPath(), this.charset != null ? this.charset : StandardCharsets.UTF_8);
+        this.pathSegments = parsePath(uri.getRawPath(), charset);
+        this.pathRootless = uri.getRawPath() != null && !uri.getRawPath().startsWith("/");
         this.encodedQuery = uri.getRawQuery();
-        this.queryParams = parseQuery(uri.getRawQuery(), this.charset != null ? this.charset : StandardCharsets.UTF_8);
+        this.queryParams = parseQuery(uri.getRawQuery(), charset, false);
         this.encodedFragment = uri.getRawFragment();
         this.fragment = uri.getFragment();
-    }
-
-    private void encodeUserInfo(final StringBuilder buf, final String userInfo) {
-        URLEncodedUtils.encUserInfo(buf, userInfo, this.charset != null ? this.charset : StandardCharsets.UTF_8);
-    }
-
-    private void encodePath(final StringBuilder buf, final List<String> pathSegments) {
-        URLEncodedUtils.formatSegments(buf, pathSegments, this.charset != null ? this.charset : StandardCharsets.UTF_8);
-    }
-
-    private void encodeUrlForm(final StringBuilder buf, final List<NameValuePair> params) {
-        URLEncodedUtils.formatParameters(buf, params, this.charset != null ? this.charset : StandardCharsets.UTF_8);
-    }
-
-    private void encodeUric(final StringBuilder buf, final String fragment) {
-        URLEncodedUtils.encUric(buf, fragment, this.charset != null ? this.charset : StandardCharsets.UTF_8);
+        this.charset = charset;
     }
 
     /**
@@ -301,7 +394,7 @@ public class URIBuilder {
             final StringBuilder sb = new StringBuilder(schemeSpecificPart);
             if (nvps != null && !nvps.isEmpty()) {
                 sb.append("?");
-                encodeUrlForm(sb, nvps);
+                formatQuery(sb, nvps, this.charset, false);
             }
             this.encodedSchemeSpecificPart = sb.toString();
         }
@@ -327,7 +420,11 @@ public class URIBuilder {
      * be unescaped and may contain non ASCII characters.
      *
      * @return this.
+     *
+     * @deprecated The use of clear-text passwords in {@link URI}s has been deprecated and is strongly
+     * discouraged.
      */
+    @Deprecated
     public URIBuilder setUserInfo(final String username, final String password) {
         return setUserInfo(username + ':' + password);
     }
@@ -387,7 +484,9 @@ public class URIBuilder {
      * @return this.
      */
     public URIBuilder setPath(final String path) {
-        return setPathSegments(path != null ? URLEncodedUtils.splitPathSegments(path) : null);
+        setPathSegments(path != null ? splitPath(path) : null);
+        this.pathRootless = path != null && !path.startsWith("/");
+        return this;
     }
 
     /**
@@ -399,6 +498,23 @@ public class URIBuilder {
         this.pathSegments = pathSegments.length > 0 ? Arrays.asList(pathSegments) : null;
         this.encodedSchemeSpecificPart = null;
         this.encodedPath = null;
+        this.pathRootless = false;
+        return this;
+    }
+
+    /**
+     * Sets rootless URI path (the first segment does not start with a /).
+     * The value is expected to be unescaped and may contain non ASCII characters.
+     *
+     * @return this.
+     *
+     * @since 5.1
+     */
+    public URIBuilder setPathSegmentsRootless(final String... pathSegments) {
+        this.pathSegments = pathSegments.length > 0 ? Arrays.asList(pathSegments) : null;
+        this.encodedSchemeSpecificPart = null;
+        this.encodedPath = null;
+        this.pathRootless = true;
         return this;
     }
 
@@ -411,6 +527,23 @@ public class URIBuilder {
         this.pathSegments = pathSegments != null && pathSegments.size() > 0 ? new ArrayList<>(pathSegments) : null;
         this.encodedSchemeSpecificPart = null;
         this.encodedPath = null;
+        this.pathRootless = false;
+        return this;
+    }
+
+    /**
+     * Sets rootless URI path (the first segment does not start with a /).
+     * The value is expected to be unescaped and may contain non ASCII characters.
+     *
+     * @return this.
+     *
+     * @since 5.1
+     */
+    public URIBuilder setPathSegmentsRootless(final List<String> pathSegments) {
+        this.pathSegments = pathSegments != null && pathSegments.size() > 0 ? new ArrayList<>(pathSegments) : null;
+        this.encodedSchemeSpecificPart = null;
+        this.encodedPath = null;
+        this.pathRootless = true;
         return this;
     }
 
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/URLEncodedUtils.java b/httpcore5/src/main/java/org/apache/hc/core5/net/URLEncodedUtils.java
index c2131ea..a3e3676 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/net/URLEncodedUtils.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/URLEncodedUtils.java
@@ -28,14 +28,11 @@
 package org.apache.hc.core5.net;
 
 import java.net.URI;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
-import java.util.Collections;
 import java.util.List;
 
 import org.apache.hc.core5.http.NameValuePair;
@@ -47,18 +44,15 @@ import org.apache.hc.core5.util.Tokenizer;
  * A collection of utilities for encoding URLs.
  *
  * @since 4.0
+ *
+ * @deprecated Use {@link URIBuilder} to parse and format {@link URI}s and
+ * {@link WWWFormCodec} to parse and format {@code application/x-www-form-urlencoded} forms.
  */
+@Deprecated
 public class URLEncodedUtils {
 
     private static final char QP_SEP_A = '&';
     private static final char QP_SEP_S = ';';
-    private static final String NAME_VALUE_SEPARATOR = "=";
-    private static final char PATH_SEPARATOR = '/';
-
-    private static final BitSet PATH_SEPARATORS     = new BitSet(256);
-    static {
-        PATH_SEPARATORS.set(PATH_SEPARATOR);
-    }
 
     /**
      * Returns a list of {@link NameValuePair}s URI query parameters.
@@ -76,7 +70,7 @@ public class URLEncodedUtils {
         if (query != null && !query.isEmpty()) {
             return parse(query, charset);
         }
-        return createEmptyList();
+        return new ArrayList<>(0);
     }
 
     /**
@@ -91,7 +85,7 @@ public class URLEncodedUtils {
      */
     public static List<NameValuePair> parse(final CharSequence s, final Charset charset) {
         if (s == null) {
-            return createEmptyList();
+            return new ArrayList<>(0);
         }
         return parse(s, charset, QP_SEP_A, QP_SEP_S);
     }
@@ -133,45 +127,13 @@ public class URLEncodedUtils {
             }
             if (!name.isEmpty()) {
                 list.add(new BasicNameValuePair(
-                        decodeFormFields(name, charset),
-                        decodeFormFields(value, charset)));
-            }
-        }
-        return list;
-    }
-
-    static List<String> splitSegments(final CharSequence s, final BitSet separators) {
-        final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
-        // Skip leading separator
-        if (cursor.atEnd()) {
-            return Collections.emptyList();
-        }
-        if (separators.get(s.charAt(cursor.getPos()))) {
-            cursor.updatePos(cursor.getPos() + 1);
-        }
-        final List<String> list = new ArrayList<>();
-        final StringBuilder buf = new StringBuilder();
-        for (;;) {
-            if (cursor.atEnd()) {
-                list.add(buf.toString());
-                break;
-            }
-            final char current = s.charAt(cursor.getPos());
-            if (separators.get(current)) {
-                list.add(buf.toString());
-                buf.setLength(0);
-            } else {
-                buf.append(current);
+                        PercentCodec.decode(name, charset, true),
+                        PercentCodec.decode(value, charset, true)));
             }
-            cursor.updatePos(cursor.getPos() + 1);
         }
         return list;
     }
 
-    static List<String> splitPathSegments(final CharSequence s) {
-        return splitSegments(s, PATH_SEPARATORS);
-    }
-
     /**
      * Returns a list of URI path segments.
      *
@@ -182,12 +144,7 @@ public class URLEncodedUtils {
      * @since 4.5
      */
     public static List<String> parsePathSegments(final CharSequence s, final Charset charset) {
-        Args.notNull(s, "Char sequence");
-        final List<String> list = splitPathSegments(s);
-        for (int i = 0; i < list.size(); i++) {
-            list.set(i, urlDecode(list.get(i), charset != null ? charset : StandardCharsets.UTF_8, false));
-        }
-        return list;
+        return URIBuilder.parsePath(s, charset);
     }
 
     /**
@@ -202,13 +159,6 @@ public class URLEncodedUtils {
         return parsePathSegments(s, StandardCharsets.UTF_8);
     }
 
-    static void formatSegments(final StringBuilder buf, final Iterable<String> segments, final Charset charset) {
-        for (final String segment : segments) {
-            buf.append(PATH_SEPARATOR);
-            urlEncode(buf, segment, charset, PATHSAFE);
-        }
-    }
-
     /**
      * Returns a string consisting of joint encoded path segments.
      *
@@ -221,7 +171,7 @@ public class URLEncodedUtils {
     public static String formatSegments(final Iterable<String> segments, final Charset charset) {
         Args.notNull(segments, "Segments");
         final StringBuilder buf = new StringBuilder();
-        formatSegments(buf, segments, charset);
+        URIBuilder.formatPath(buf, segments, false, charset);
         return buf.toString();
     }
 
@@ -237,32 +187,6 @@ public class URLEncodedUtils {
         return formatSegments(Arrays.asList(segments), StandardCharsets.UTF_8);
     }
 
-    static void formatNameValuePairs(
-            final StringBuilder buf,
-            final Iterable<? extends NameValuePair> parameters,
-            final char parameterSeparator,
-            final Charset charset) {
-        int i = 0;
-        for (final NameValuePair parameter : parameters) {
-            if (i > 0) {
-                buf.append(parameterSeparator);
-            }
-            encodeFormFields(buf, parameter.getName(), charset);
-            if (parameter.getValue() != null) {
-                buf.append(NAME_VALUE_SEPARATOR);
-                encodeFormFields(buf, parameter.getValue(), charset);
-            }
-            i++;
-        }
-    }
-
-    static void formatParameters(
-            final StringBuilder buf,
-            final Iterable<? extends NameValuePair> parameters,
-            final Charset charset) {
-        formatNameValuePairs(buf, parameters, QP_SEP_A, charset);
-    }
-
     /**
      * Returns a String that is suitable for use as an {@code application/x-www-form-urlencoded}
      * list of parameters in an HTTP PUT or HTTP POST.
@@ -280,7 +204,18 @@ public class URLEncodedUtils {
             final Charset charset) {
         Args.notNull(parameters, "Parameters");
         final StringBuilder buf = new StringBuilder();
-        formatNameValuePairs(buf, parameters, parameterSeparator, charset);
+        int i = 0;
+        for (final NameValuePair parameter : parameters) {
+            if (i > 0) {
+                buf.append(parameterSeparator);
+            }
+            PercentCodec.encode(buf, parameter.getName(), charset, URLENCODER, true);
+            if (parameter.getValue() != null) {
+                buf.append('=');
+                PercentCodec.encode(buf, parameter.getValue(), charset, URLENCODER, true);
+            }
+            i++;
+        }
         return buf.toString();
     }
 
@@ -300,205 +235,25 @@ public class URLEncodedUtils {
         return format(parameters, QP_SEP_A, charset);
     }
 
-    /**
-     * Unreserved characters, i.e. alphanumeric, plus: {@code _ - ! . ~ ' ( ) *}
-     * <p>
-     *  This list is the same as the {@code unreserved} list in
-     *  <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>
-     */
-    private static final BitSet UNRESERVED   = new BitSet(256);
-    /**
-     * Punctuation characters: , ; : $ & + =
-     * <p>
-     * These are the additional characters allowed by userinfo.
-     */
-    private static final BitSet PUNCT        = new BitSet(256);
-    /** Characters which are safe to use in userinfo,
-     * i.e. {@link #UNRESERVED} plus {@link #PUNCT}uation */
-    private static final BitSet USERINFO     = new BitSet(256);
-    /** Characters which are safe to use in a path,
-     * i.e. {@link #UNRESERVED} plus {@link #PUNCT}uation plus / @ */
-    private static final BitSet PATHSAFE     = new BitSet(256);
-    /** Characters which are safe to use in a query or a fragment,
-     * i.e. {@link #RESERVED} plus {@link #UNRESERVED} */
-    private static final BitSet URIC     = new BitSet(256);
-
-    /**
-     * Reserved characters, i.e. {@code ;/?:@&=+$,[]}
-     * <p>
-     *  This list is the same as the {@code reserved} list in
-     *  <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>
-     *  as augmented by
-     *  <a href="http://www.ietf.org/rfc/rfc2732.txt">RFC 2732</a>
-     */
-    private static final BitSet RESERVED     = new BitSet(256);
-
-
-    /**
-     * Safe characters for x-www-form-urlencoded data, as per java.net.URLEncoder and browser behaviour,
-     * i.e. alphanumeric plus {@code "-", "_", ".", "*"}
-     */
     private static final BitSet URLENCODER   = new BitSet(256);
 
-    private static final BitSet PATH_SPECIAL = new BitSet(256);
-
     static {
         // unreserved chars
         // alpha characters
         for (int i = 'a'; i <= 'z'; i++) {
-            UNRESERVED.set(i);
+            URLENCODER.set(i);
         }
         for (int i = 'A'; i <= 'Z'; i++) {
-            UNRESERVED.set(i);
+            URLENCODER.set(i);
         }
         // numeric characters
         for (int i = '0'; i <= '9'; i++) {
-            UNRESERVED.set(i);
-        }
-        UNRESERVED.set('_'); // these are the charactes of the "mark" list
-        UNRESERVED.set('-');
-        UNRESERVED.set('.');
-        UNRESERVED.set('*');
-        URLENCODER.or(UNRESERVED); // skip remaining unreserved characters
-        UNRESERVED.set('!');
-        UNRESERVED.set('~');
-        UNRESERVED.set('\'');
-        UNRESERVED.set('(');
-        UNRESERVED.set(')');
-        // punct chars
-        PUNCT.set(',');
-        PUNCT.set(';');
-        PUNCT.set(':');
-        PUNCT.set('$');
-        PUNCT.set('&');
-        PUNCT.set('+');
-        PUNCT.set('=');
-        // Safe for userinfo
-        USERINFO.or(UNRESERVED);
-        USERINFO.or(PUNCT);
-
-        // URL path safe
-        PATHSAFE.or(UNRESERVED);
-        PATHSAFE.set(';'); // param separator
-        PATHSAFE.set(':'); // RFC 2396
-        PATHSAFE.set('@');
-        PATHSAFE.set('&');
-        PATHSAFE.set('=');
-        PATHSAFE.set('+');
-        PATHSAFE.set('$');
-        PATHSAFE.set(',');
-
-        PATH_SPECIAL.or(PATHSAFE);
-        PATH_SPECIAL.set('/');
-
-        RESERVED.set(';');
-        RESERVED.set('/');
-        RESERVED.set('?');
-        RESERVED.set(':');
-        RESERVED.set('@');
-        RESERVED.set('&');
-        RESERVED.set('=');
-        RESERVED.set('+');
-        RESERVED.set('$');
-        RESERVED.set(',');
-        RESERVED.set('['); // added by RFC 2732
-        RESERVED.set(']'); // added by RFC 2732
-
-        URIC.or(RESERVED);
-        URIC.or(UNRESERVED);
-    }
-
-    private static final int RADIX = 16;
-
-    private static List<NameValuePair> createEmptyList() {
-        return new ArrayList<>(0);
-    }
-
-    private static void urlEncode(
-            final StringBuilder buf,
-            final String content,
-            final Charset charset,
-            final BitSet safechars) {
-        if (content == null) {
-            return;
-        }
-        final ByteBuffer bb = charset.encode(content);
-        while (bb.hasRemaining()) {
-            final int b = bb.get() & 0xff;
-            if (safechars.get(b)) {
-                buf.append((char) b);
-            } else {
-                buf.append("%");
-                final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX));
-                final char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, RADIX));
-                buf.append(hex1);
-                buf.append(hex2);
-            }
-        }
-    }
-
-    private static String urlDecode(
-            final String content,
-            final Charset charset,
-            final boolean plusAsBlank) {
-        if (content == null) {
-            return null;
-        }
-        final ByteBuffer bb = ByteBuffer.allocate(content.length());
-        final CharBuffer cb = CharBuffer.wrap(content);
-        while (cb.hasRemaining()) {
-            final char c = cb.get();
-            if (c == '%' && cb.remaining() >= 2) {
-                final char uc = cb.get();
-                final char lc = cb.get();
-                final int u = Character.digit(uc, 16);
-                final int l = Character.digit(lc, 16);
-                if (u != -1 && l != -1) {
-                    bb.put((byte) ((u << 4) + l));
-                } else {
-                    bb.put((byte) '%');
-                    bb.put((byte) uc);
-                    bb.put((byte) lc);
-                }
-            } else if (plusAsBlank && c == '+') {
-                bb.put((byte) ' ');
-            } else {
-                bb.put((byte) c);
-            }
-        }
-        bb.flip();
-        return charset.decode(bb).toString();
-    }
-
-    static String decodeFormFields(final String content, final Charset charset) {
-        if (content == null) {
-            return null;
+            URLENCODER.set(i);
         }
-        return urlDecode(content, charset != null ? charset : StandardCharsets.UTF_8, true);
-    }
-
-    static void encodeFormFields(final StringBuilder buf, final String content, final Charset charset) {
-        if (content == null) {
-            return;
-        }
-        urlEncode(buf, content, charset != null ? charset : StandardCharsets.UTF_8, URLENCODER);
-    }
-
-    static String encodeFormFields(final String content, final Charset charset) {
-        if (content == null) {
-            return null;
-        }
-        final StringBuilder buf = new StringBuilder();
-        urlEncode(buf, content, charset != null ? charset : StandardCharsets.UTF_8, URLENCODER);
-        return buf.toString();
-    }
-
-    static void encUserInfo(final StringBuilder buf, final String content, final Charset charset) {
-        urlEncode(buf, content, charset != null ? charset : StandardCharsets.UTF_8, USERINFO);
-    }
-
-    static void encUric(final StringBuilder buf, final String content, final Charset charset) {
-        urlEncode(buf, content, charset != null ? charset : StandardCharsets.UTF_8, URIC);
+        URLENCODER.set('_'); // these are the characters of the "mark" list
+        URLENCODER.set('-');
+        URLENCODER.set('.');
+        URLENCODER.set('*');
     }
 
 }
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
new file mode 100644
index 0000000..900918e
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/WWWFormCodec.java
@@ -0,0 +1,83 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.net;
+
+import java.nio.charset.Charset;
+import java.util.List;
+
+import org.apache.hc.core5.http.NameValuePair;
+
+/**
+ * {@code application/x-www-form-urlencoded} codec.
+ *
+ * @since 5.1
+ */
+public class WWWFormCodec {
+
+    private static final char QP_SEP_A = '&';
+
+    /**
+     * Returns a list of {@link NameValuePair} parameters parsed
+     * from the {@code application/x-www-form-urlencoded} content.
+     *
+     * @param s input text.
+     * @param charset parameter charset.
+     * @return list of form parameters.
+     */
+    public static List<NameValuePair> parse(final CharSequence s, final Charset charset) {
+        return URIBuilder.parseQuery(s, charset, true);
+    }
+
+    /**
+     * Formats the list of {@link NameValuePair} parameters into a {@code application/x-www-form-urlencoded}
+     * content.
+     *
+     * @param buf the content buffer
+     * @param params  The from parameters.
+     * @param charset The encoding to use.
+     */
+    public static void format(
+            final StringBuilder buf, final Iterable<? extends NameValuePair> params, final Charset charset) {
+        URIBuilder.formatQuery(buf, params, charset, true);
+    }
+
+    /**
+     * Formats the list of {@link NameValuePair} parameters into a {@code application/x-www-form-urlencoded}
+     * content string.
+     *
+     * @param params  The from parameters.
+     * @param charset The encoding to use.
+     * @return content string
+     */
+    public static String format(final Iterable<? extends NameValuePair> params, final Charset charset) {
+        final StringBuilder buf = new StringBuilder();
+        URIBuilder.formatQuery(buf, params, charset, true);
+        return buf.toString();
+    }
+
+}
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/NameValuePairListMatcher.java b/httpcore5/src/test/java/org/apache/hc/core5/http/NameValuePairListMatcher.java
new file mode 100644
index 0000000..7ecf449
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/NameValuePairListMatcher.java
@@ -0,0 +1,85 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.core5.http;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.hc.core5.util.LangUtils;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+
+public class NameValuePairListMatcher extends BaseMatcher<List<NameValuePair>> {
+
+    private List<? extends NameValuePair> nvps;
+
+    public NameValuePairListMatcher(final List<? extends NameValuePair> nvps) {
+        this.nvps = nvps;
+    }
+
+    @Override
+    public boolean matches(final Object item) {
+        if (item instanceof List<?>) {
+            final List<?> objects = (List<?>) item;
+            if (objects.size() != nvps.size()) {
+                return false;
+            }
+            for (int i = 1; i < objects.size(); i++) {
+                final Object obj = objects.get(i);
+                if (obj instanceof NameValuePair) {
+                    final NameValuePair nvp = (NameValuePair) obj;
+                    final NameValuePair expected = nvps.get(i);
+                    if (!LangUtils.equals(nvp.getName(), expected.getName())
+                            || !LangUtils.equals(nvp.getValue(), expected.getValue())) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void describeTo(final Description description) {
+        description.appendText("equals ").appendValueList("[", ";", "]", nvps);
+    }
+
+    @Factory
+    public static Matcher<List<NameValuePair>> equalsTo(final NameValuePair... nvps) {
+        return new NameValuePairListMatcher(Arrays.asList(nvps));
+    }
+
+    @Factory
+    public static Matcher<List<NameValuePair>> isEmpty() {
+        return new NameValuePairListMatcher(Collections.<NameValuePair>emptyList());
+    }
+
+}
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 d50131c..f83ac7f 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
@@ -42,7 +42,7 @@ import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.http.message.BasicNameValuePair;
-import org.apache.hc.core5.net.URLEncodedUtils;
+import org.apache.hc.core5.net.WWWFormCodec;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -228,7 +228,7 @@ public class TestEntityUtils {
         parameters.add(new BasicNameValuePair("russian", ru_hello));
         parameters.add(new BasicNameValuePair("swiss", ch_hello));
 
-        final String s = URLEncodedUtils.format(parameters, StandardCharsets.UTF_8);
+        final String s = WWWFormCodec.format(parameters, StandardCharsets.UTF_8);
 
         Assert.assertEquals("russian=%D0%92%D1%81%D0%B5%D0%BC_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82" +
                 "&swiss=Gr%C3%BCezi_z%C3%A4m%C3%A4", s);
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
new file mode 100644
index 0000000..a19b617
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestPercentCodec.java
@@ -0,0 +1,70 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.net;
+
+import java.nio.charset.StandardCharsets;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link PercentCodec}.
+ */
+public class TestPercentCodec {
+
+    @Test
+    public void testCoding() {
+        final StringBuilder buf = new StringBuilder();
+        PercentCodec.encode(buf, "blah!", StandardCharsets.UTF_8);
+        PercentCodec.encode(buf, " ~ ", StandardCharsets.UTF_8);
+        PercentCodec.encode(buf, "huh?", StandardCharsets.UTF_8);
+        MatcherAssert.assertThat(buf.toString(), CoreMatchers.equalTo("blah%21%20~%20huh%3F"));
+    }
+
+    @Test
+    public void testDecoding() {
+        MatcherAssert.assertThat(PercentCodec.decode("blah%21%20~%20huh%3F", StandardCharsets.UTF_8),
+                CoreMatchers.equalTo("blah! ~ huh?"));
+        MatcherAssert.assertThat(PercentCodec.decode("blah%21+~%20huh%3F", StandardCharsets.UTF_8),
+                CoreMatchers.equalTo("blah!+~ huh?"));
+        MatcherAssert.assertThat(PercentCodec.decode("blah%21+~%20huh%3F", StandardCharsets.UTF_8, true),
+                CoreMatchers.equalTo("blah! ~ huh?"));
+    }
+
+    @Test
+    public void testDecodingPartialContent() {
+        MatcherAssert.assertThat(PercentCodec.decode("blah%21%20%", StandardCharsets.UTF_8),
+                CoreMatchers.equalTo("blah! %"));
+        MatcherAssert.assertThat(PercentCodec.decode("blah%21%20%a", StandardCharsets.UTF_8),
+                CoreMatchers.equalTo("blah! %a"));
+        MatcherAssert.assertThat(PercentCodec.decode("blah%21%20%wa", StandardCharsets.UTF_8),
+                CoreMatchers.equalTo("blah! %wa"));
+    }
+
+}
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 f86d345..ea153cf 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
@@ -32,17 +32,138 @@ import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.NameValuePairListMatcher;
 import org.apache.hc.core5.http.message.BasicNameValuePair;
 import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
 import org.junit.Assert;
 import org.junit.Test;
 
 public class TestURIBuilder {
 
+    private static final String CH_HELLO = "\u0047\u0072\u00FC\u0065\u007A\u0069\u005F\u007A\u00E4\u006D\u00E4";
+    private static final String RU_HELLO = "\u0412\u0441\u0435\u043C\u005F\u043F\u0440\u0438\u0432\u0435\u0442";
+
+    static List<String> parsePath(final CharSequence s) {
+        return URIBuilder.parsePath(s, null);
+    }
+
+    @Test
+    public void testParseSegments() throws Exception {
+        MatcherAssert.assertThat(parsePath("/this/that"), CoreMatchers.equalTo(Arrays.asList("this", "that")));
+        MatcherAssert.assertThat(parsePath("this/that"), CoreMatchers.equalTo(Arrays.asList("this", "that")));
+        MatcherAssert.assertThat(parsePath("this//that"), CoreMatchers.equalTo(Arrays.asList("this", "", "that")));
+        MatcherAssert.assertThat(parsePath("this//that/"), CoreMatchers.equalTo(Arrays.asList("this", "", "that", "")));
+        MatcherAssert.assertThat(parsePath("this//that/%2fthis%20and%20that"),
+                CoreMatchers.equalTo(Arrays.asList("this", "", "that", "/this and that")));
+        MatcherAssert.assertThat(parsePath("this///that//"),
+                CoreMatchers.equalTo(Arrays.asList("this", "", "", "that", "", "")));
+        MatcherAssert.assertThat(parsePath("/"), CoreMatchers.equalTo(Collections.singletonList("")));
+        MatcherAssert.assertThat(parsePath(""), CoreMatchers.equalTo(Collections.<String>emptyList()));
+    }
+
+    static String formatPath(final String... pathSegments) {
+        final StringBuilder buf = new StringBuilder();
+        URIBuilder.formatPath(buf, Arrays.asList(pathSegments), false, null);
+        return buf.toString();
+    }
+
+    @Test
+    public void testFormatSegments() throws Exception {
+        MatcherAssert.assertThat(formatPath("this", "that"), CoreMatchers.equalTo("/this/that"));
+        MatcherAssert.assertThat(formatPath("this", "", "that"), CoreMatchers.equalTo("/this//that"));
+        MatcherAssert.assertThat(formatPath("this", "", "that", "/this and that"),
+                CoreMatchers.equalTo("/this//that/%2Fthis%20and%20that"));
+        MatcherAssert.assertThat(formatPath("this", "", "", "that", "", ""),
+                CoreMatchers.equalTo("/this///that//"));
+        MatcherAssert.assertThat(formatPath(""), CoreMatchers.equalTo("/"));
+        MatcherAssert.assertThat(formatPath(), CoreMatchers.equalTo(""));
+    }
+
+    static List<NameValuePair> parseQuery(final CharSequence s) {
+        return URIBuilder.parseQuery(s, null, false);
+    }
+
+    @Test
+    public void testParseQuery() throws Exception {
+        MatcherAssert.assertThat(parseQuery(""), NameValuePairListMatcher.isEmpty());
+        MatcherAssert.assertThat(parseQuery("Name0"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name0", null)));
+        MatcherAssert.assertThat(parseQuery("Name1=Value1"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name1", "Value1")));
+        MatcherAssert.assertThat(parseQuery("Name2="),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name2", "")));
+        MatcherAssert.assertThat(parseQuery(" Name3  "),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name3", null)));
+        MatcherAssert.assertThat(parseQuery("Name4=Value%204%21"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name4", "Value 4!")));
+        MatcherAssert.assertThat(parseQuery("Name4=Value%2B4%21"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name4", "Value+4!")));
+        MatcherAssert.assertThat(parseQuery("Name4=Value%204%21%20%214"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name4", "Value 4! !4")));
+        MatcherAssert.assertThat(parseQuery("Name5=aaa&Name6=bbb"),
+                NameValuePairListMatcher.equalsTo(
+                        new BasicNameValuePair("Name5", "aaa"),
+                        new BasicNameValuePair("Name6", "bbb")));
+        MatcherAssert.assertThat(parseQuery("Name7=aaa&Name7=b%2Cb&Name7=ccc"),
+                NameValuePairListMatcher.equalsTo(
+                        new BasicNameValuePair("Name7", "aaa"),
+                        new BasicNameValuePair("Name7", "b,b"),
+                        new BasicNameValuePair("Name7", "ccc")));
+        MatcherAssert.assertThat(parseQuery("Name8=xx%2C%20%20yy%20%20%2Czz"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name8", "xx,  yy  ,zz")));
+        MatcherAssert.assertThat(parseQuery("price=10%20%E2%82%AC"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("price", "10 \u20AC")));
+        MatcherAssert.assertThat(parseQuery("a=b\"c&d=e"),
+                NameValuePairListMatcher.equalsTo(
+                        new BasicNameValuePair("a", "b\"c"),
+                        new BasicNameValuePair("d", "e")));
+        MatcherAssert.assertThat(parseQuery("russian=" + PercentCodec.encode(RU_HELLO, StandardCharsets.UTF_8) +
+                        "&swiss=" + PercentCodec.encode(CH_HELLO, StandardCharsets.UTF_8)),
+                NameValuePairListMatcher.equalsTo(
+                        new BasicNameValuePair("russian", RU_HELLO),
+                        new BasicNameValuePair("swiss", CH_HELLO)));
+    }
+
+    static String formatQuery(final NameValuePair... params) {
+        final StringBuilder buf = new StringBuilder();
+        URIBuilder.formatQuery(buf, Arrays.asList(params), null, false);
+        return buf.toString();
+    }
+
+    @Test
+    public void testFormatQuery() throws Exception {
+        MatcherAssert.assertThat(formatQuery(new BasicNameValuePair("Name0", null)), CoreMatchers.equalTo("Name0"));
+        MatcherAssert.assertThat(formatQuery(new BasicNameValuePair("Name1", "Value1")), CoreMatchers.equalTo("Name1=Value1"));
+        MatcherAssert.assertThat(formatQuery(new BasicNameValuePair("Name2", "")), CoreMatchers.equalTo("Name2="));
+        MatcherAssert.assertThat(formatQuery(new BasicNameValuePair("Name4", "Value 4&")),
+                CoreMatchers.equalTo("Name4=Value%204%26"));
+        MatcherAssert.assertThat(formatQuery(new BasicNameValuePair("Name4", "Value+4&")),
+                CoreMatchers.equalTo("Name4=Value%2B4%26"));
+        MatcherAssert.assertThat(formatQuery(new BasicNameValuePair("Name4", "Value 4& =4")),
+                CoreMatchers.equalTo("Name4=Value%204%26%20%3D4"));
+        MatcherAssert.assertThat(formatQuery(
+                new BasicNameValuePair("Name5", "aaa"),
+                new BasicNameValuePair("Name6", "bbb")), CoreMatchers.equalTo("Name5=aaa&Name6=bbb"));
+        MatcherAssert.assertThat(formatQuery(
+                new BasicNameValuePair("Name7", "aaa"),
+                new BasicNameValuePair("Name7", "b,b"),
+                new BasicNameValuePair("Name7", "ccc")
+        ), CoreMatchers.equalTo("Name7=aaa&Name7=b%2Cb&Name7=ccc"));
+        MatcherAssert.assertThat(formatQuery(new BasicNameValuePair("Name8", "xx,  yy  ,zz")),
+                CoreMatchers.equalTo("Name8=xx%2C%20%20yy%20%20%2Czz"));
+        MatcherAssert.assertThat(formatQuery(
+                new BasicNameValuePair("russian", RU_HELLO),
+                new BasicNameValuePair("swiss", CH_HELLO)),
+                CoreMatchers.equalTo("russian=" + PercentCodec.encode(RU_HELLO, StandardCharsets.UTF_8) +
+                        "&swiss=" + PercentCodec.encode(CH_HELLO, StandardCharsets.UTF_8)));
+    }
+
     @Test
     public void testHierarchicalUri() throws Exception {
         final URI uri = new URI("http", "stuff", "localhost", 80, "/some stuff", "param=stuff", "fragment");
@@ -169,14 +290,6 @@ public class TestURIBuilder {
     }
 
     @Test
-    public void testSetUserInfo() throws Exception {
-        final URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff", null);
-        final URIBuilder uribuilder = new URIBuilder(uri).setUserInfo("user", "password");
-        final URI result = uribuilder.build();
-        Assert.assertEquals(new URI("http://user:password@localhost:80/?param=stuff"), result);
-    }
-
-    @Test
     public void testRemoveParameters() throws Exception {
         final URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff", null);
         final URIBuilder uribuilder = new URIBuilder(uri).removeQuery();
@@ -290,35 +403,6 @@ public class TestURIBuilder {
     }
 
     @Test
-    public void testAgainstURIEncoded() throws Exception {
-        // Check that the encoded URI generated by URI builder agrees with that generated by using URI directly
-        final String scheme="https";
-        final String host="localhost";
-        final String specials="/ abcd!$&*()_-+.,=:;'~<>/@[]|#^%\"{}\\`xyz"; // N.B. excludes \u00a3\u00ac\u00a6
-        final URI uri = new URI(scheme, specials, host, 80, specials, specials, specials);
-
-        final URI bld = new URIBuilder()
-                .setScheme(scheme)
-                .setHost(host)
-                .setUserInfo(specials)
-                .setPath(specials)
-                .setCustomQuery(specials)
-                .setFragment(specials)
-                .build();
-
-        Assert.assertEquals(uri.getHost(), bld.getHost());
-
-        Assert.assertEquals(uri.getRawUserInfo(), bld.getRawUserInfo());
-
-        Assert.assertEquals(uri.getRawPath(), bld.getRawPath());
-
-        Assert.assertEquals(uri.getRawQuery(), bld.getRawQuery());
-
-        Assert.assertEquals(uri.getRawFragment(), bld.getRawFragment());
-
-    }
-
-    @Test
     public void testBuildAddParametersUTF8() throws Exception {
         assertAddParameters(StandardCharsets.UTF_8);
     }
@@ -355,8 +439,8 @@ public class TestURIBuilder {
     }
 
     public void assertBuild(final Charset charset, final URI uri) throws Exception {
-        final String encodedData1 = URLEncodedUtils.encodeFormFields("\"1\u00aa position\"", charset);
-        final String encodedData2 = URLEncodedUtils.encodeFormFields("Jos\u00e9 Abra\u00e3o", charset);
+        final String encodedData1 = PercentCodec.encode("\"1\u00aa position\"", charset);
+        final String encodedData2 = PercentCodec.encode("Jos\u00e9 Abra\u00e3o", charset);
 
         final String uriExpected = String.format("https://somehost.com/stuff?parameter1=value1&parameter2=%s&parameter3=%s", encodedData1, encodedData2);
 
@@ -392,7 +476,7 @@ public class TestURIBuilder {
 
     @Test
     public void testTolerateNullInput() throws Exception {
-        Assert.assertThat(new URIBuilder()
+        MatcherAssert.assertThat(new URIBuilder()
                         .setScheme(null)
                         .setHost("localhost")
                         .setUserInfo(null)
@@ -406,7 +490,7 @@ public class TestURIBuilder {
 
     @Test
     public void testTolerateBlankInput() throws Exception {
-        Assert.assertThat(new URIBuilder()
+        MatcherAssert.assertThat(new URIBuilder()
                         .setScheme("")
                         .setHost("localhost")
                         .setUserInfo("")
@@ -424,8 +508,7 @@ public class TestURIBuilder {
         final HttpHost httpHost = new HttpHost("http", "example.com", 1234);
         final URIBuilder uribuilder = new URIBuilder();
         uribuilder.setHttpHost(httpHost);
-        final URI result = uribuilder.build();
-        Assert.assertEquals(URI.create(httpHost.toURI()), result);
+        Assert.assertEquals(URI.create("http://example.com:1234"), uribuilder.build());
     }
 
     @Test
@@ -435,23 +518,50 @@ public class TestURIBuilder {
                 .setHost("somehost")
                 .setPath("//blah//blah")
                 .build();
-        Assert.assertThat(uri, CoreMatchers.equalTo(URI.create("ftp://somehost//blah//blah")));
+        MatcherAssert.assertThat(uri, CoreMatchers.equalTo(URI.create("ftp://somehost//blah//blah")));
     }
 
     @Test
-    public void testPathNoLeadingSlash() throws Exception {
+    public void testNoAuthorityAndPath() throws Exception {
         final URI uri = new URIBuilder()
-                .setScheme("ftp")
+                .setScheme("file")
+                .setPath("/blah")
+                .build();
+        MatcherAssert.assertThat(uri, CoreMatchers.equalTo(URI.create("file:/blah")));
+    }
+
+    @Test
+    public void testNoAuthorityAndPathSegments() throws Exception {
+        final URI uri = new URIBuilder()
+                .setScheme("file")
+                .setPathSegments("this", "that")
+                .build();
+        MatcherAssert.assertThat(uri, CoreMatchers.equalTo(URI.create("file:/this/that")));
+    }
+
+    @Test
+    public void testNoAuthorityAndRootlessPath() throws Exception {
+        final URI uri = new URIBuilder()
+                .setScheme("file")
                 .setPath("blah")
                 .build();
-        Assert.assertThat(uri, CoreMatchers.equalTo(URI.create("ftp:/blah")));
+        MatcherAssert.assertThat(uri, CoreMatchers.equalTo(URI.create("file:blah")));
+    }
+
+    @Test
+    public void testNoAuthorityAndRootlessPathSegments() throws Exception {
+        final URI uri = new URIBuilder()
+                .setScheme("file")
+                .setPathSegmentsRootless("this", "that")
+                .build();
+        MatcherAssert.assertThat(uri, CoreMatchers.equalTo(URI.create("file:this/that")));
     }
 
     @Test
     public void testOpaque() throws Exception {
         final URIBuilder uriBuilder = new URIBuilder("http://host.com");
         final URI uri = uriBuilder.build();
-        Assert.assertThat(uriBuilder.isOpaque(), CoreMatchers.equalTo(uri.isOpaque()));
+        MatcherAssert.assertThat(uriBuilder.isOpaque(), CoreMatchers.equalTo(uri.isOpaque()));
     }
 
     @Test
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestURLEncodedUtils.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestURLEncodedUtils.java
deleted file mode 100644
index e7687a4..0000000
--- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestURLEncodedUtils.java
+++ /dev/null
@@ -1,394 +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
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.hc.core5.net;
-
-import java.net.URI;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.hc.core5.http.NameValuePair;
-import org.apache.hc.core5.http.message.BasicNameValuePair;
-import org.hamcrest.CoreMatchers;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class TestURLEncodedUtils {
-
-    @Test
-    public void testParseURLCodedContent() throws Exception {
-        List <NameValuePair> result;
-
-        result = parse("");
-        Assert.assertTrue(result.isEmpty());
-
-        result = parse("Name0");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name0", null);
-
-        result = parse("Name1=Value1");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name1", "Value1");
-
-        result = parse("Name2=");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name2", "");
-
-        result = parse("Name3");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name3", null);
-
-        result = parse("Name4=Value%204%21");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name4", "Value 4!");
-
-        result = parse("Name4=Value%2B4%21");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name4", "Value+4!");
-
-        result = parse("Name4=Value%204%21%20%214");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name4", "Value 4! !4");
-
-        result = parse("Name5=aaa&Name6=bbb");
-        Assert.assertEquals(2, result.size());
-        assertNameValuePair(result.get(0), "Name5", "aaa");
-        assertNameValuePair(result.get(1), "Name6", "bbb");
-
-        result = parse("Name7=aaa&Name7=b%2Cb&Name7=ccc");
-        Assert.assertEquals(3, result.size());
-        assertNameValuePair(result.get(0), "Name7", "aaa");
-        assertNameValuePair(result.get(1), "Name7", "b,b");
-        assertNameValuePair(result.get(2), "Name7", "ccc");
-
-        result = parse("Name8=xx%2C%20%20yy%20%20%2Czz");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name8", "xx,  yy  ,zz");
-
-        result = parse("price=10%20%E2%82%AC");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "price", "10 \u20AC");
-    }
-
-    @Test
-    public void testParseSegments() throws Exception {
-        Assert.assertThat(URLEncodedUtils.parsePathSegments("/this/that"),
-                CoreMatchers.equalTo(Arrays.asList("this", "that")));
-        Assert.assertThat(URLEncodedUtils.parsePathSegments("this/that"),
-                CoreMatchers.equalTo(Arrays.asList("this", "that")));
-        Assert.assertThat(URLEncodedUtils.parsePathSegments("this//that"),
-                CoreMatchers.equalTo(Arrays.asList("this", "", "that")));
-        Assert.assertThat(URLEncodedUtils.parsePathSegments("this//that/"),
-                CoreMatchers.equalTo(Arrays.asList("this", "", "that", "")));
-        Assert.assertThat(URLEncodedUtils.parsePathSegments("this//that/%2fthis%20and%20that"),
-                CoreMatchers.equalTo(Arrays.asList("this", "", "that", "/this and that")));
-        Assert.assertThat(URLEncodedUtils.parsePathSegments("this///that//"),
-                CoreMatchers.equalTo(Arrays.asList("this", "", "", "that", "", "")));
-        Assert.assertThat(URLEncodedUtils.parsePathSegments("/"),
-                CoreMatchers.equalTo(Collections.singletonList("")));
-        Assert.assertThat(URLEncodedUtils.parsePathSegments(""),
-                CoreMatchers.equalTo(Collections.<String>emptyList()));
-    }
-
-    @Test
-    public void testFormatSegments() throws Exception {
-        Assert.assertThat(URLEncodedUtils.formatSegments("this", "that"),
-                CoreMatchers.equalTo("/this/that"));
-        Assert.assertThat(URLEncodedUtils.formatSegments("this", "", "that"),
-                CoreMatchers.equalTo("/this//that"));
-        Assert.assertThat(URLEncodedUtils.formatSegments("this", "", "that", "/this and that"),
-                CoreMatchers.equalTo("/this//that/%2Fthis%20and%20that"));
-        Assert.assertThat(URLEncodedUtils.formatSegments("this", "", "", "that", "", ""),
-                CoreMatchers.equalTo("/this///that//"));
-        Assert.assertThat(URLEncodedUtils.formatSegments(""),
-                CoreMatchers.equalTo("/"));
-        Assert.assertThat(URLEncodedUtils.formatSegments(),
-                CoreMatchers.equalTo(""));
-    }
-
-    @Test
-    public void testParseURLCodedContentString() throws Exception {
-        List <NameValuePair> result;
-
-        result = parseString("");
-        Assert.assertTrue(result.isEmpty());
-
-        result = parseString("Name0");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name0", null);
-
-        result = parseString("Name1=Value1");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name1", "Value1");
-
-        result = parseString("Name2=");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name2", "");
-
-        result = parseString("Name3");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name3", null);
-
-        result = parseString("Name4=Value%204%21");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name4", "Value 4!");
-
-        result = parseString("Name4=Value%2B4%21");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name4", "Value+4!");
-
-        result = parseString("Name4=Value%204%21%20%214");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name4", "Value 4! !4");
-
-        result = parseString("Name5=aaa&Name6=bbb");
-        Assert.assertEquals(2, result.size());
-        assertNameValuePair(result.get(0), "Name5", "aaa");
-        assertNameValuePair(result.get(1), "Name6", "bbb");
-
-        result = parseString("Name7=aaa&Name7=b%2Cb&Name7=ccc");
-        Assert.assertEquals(3, result.size());
-        assertNameValuePair(result.get(0), "Name7", "aaa");
-        assertNameValuePair(result.get(1), "Name7", "b,b");
-        assertNameValuePair(result.get(2), "Name7", "ccc");
-
-        result = parseString("Name8=xx%2C%20%20yy%20%20%2Czz");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "Name8", "xx,  yy  ,zz");
-
-        result = parseString("price=10%20%E2%82%AC");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "price", "10 \u20AC");
-
-        result = parse("a=b\"c&d=e");
-        Assert.assertEquals(2, result.size());
-        assertNameValuePair(result.get(0), "a", "b\"c");
-        assertNameValuePair(result.get(1), "d", "e");
-    }
-
-    @Test
-    public void testParseInvalidURLCodedContent() throws Exception {
-        List <NameValuePair> result;
-
-        result = parse("name=%");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "name", "%");
-
-        result = parse("name=%a");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "name", "%a");
-
-        result = parse("name=%wa%20");
-        Assert.assertEquals(1, result.size());
-        assertNameValuePair(result.get(0), "name", "%wa ");
-    }
-
-    private static final int SWISS_GERMAN_HELLO [] = {
-        0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4
-    };
-
-    private static final int RUSSIAN_HELLO [] = {
-        0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438,
-        0x432, 0x435, 0x442
-    };
-
-    private static String constructString(final int [] unicodeChars) {
-        final StringBuilder buffer = new StringBuilder();
-        if (unicodeChars != null) {
-            for (final int unicodeChar : unicodeChars) {
-                buffer.append((char)unicodeChar);
-            }
-        }
-        return buffer.toString();
-    }
-
-    @Test
-    public void testParseUTF8Ampersand1String() throws Exception {
-        final String ru_hello = constructString(RUSSIAN_HELLO);
-        final String ch_hello = constructString(SWISS_GERMAN_HELLO);
-        final List <NameValuePair> parameters = new ArrayList<>();
-        parameters.add(new BasicNameValuePair("russian", ru_hello));
-        parameters.add(new BasicNameValuePair("swiss", ch_hello));
-
-        final String s = URLEncodedUtils.format(parameters, StandardCharsets.UTF_8);
-
-        final List <NameValuePair> result = URLEncodedUtils.parse(s, StandardCharsets.UTF_8);
-        Assert.assertEquals(2, result.size());
-        assertNameValuePair(result.get(0), "russian", ru_hello);
-        assertNameValuePair(result.get(1), "swiss", ch_hello);
-    }
-
-    @Test
-    public void testParseUTF8Ampersand2String() throws Exception {
-        testParseUTF8String('&');
-    }
-
-    @Test
-    public void testParseUTF8SemicolonString() throws Exception {
-        testParseUTF8String(';');
-    }
-
-    private void testParseUTF8String(final char parameterSeparator) throws Exception {
-        final String ru_hello = constructString(RUSSIAN_HELLO);
-        final String ch_hello = constructString(SWISS_GERMAN_HELLO);
-        final List <NameValuePair> parameters = new ArrayList<>();
-        parameters.add(new BasicNameValuePair("russian", ru_hello));
-        parameters.add(new BasicNameValuePair("swiss", ch_hello));
-
-        final String s = URLEncodedUtils.format(parameters, parameterSeparator, StandardCharsets.UTF_8);
-
-        final List<NameValuePair> result1 = URLEncodedUtils.parse(s, StandardCharsets.UTF_8);
-        Assert.assertEquals(2, result1.size());
-        assertNameValuePair(result1.get(0), "russian", ru_hello);
-        assertNameValuePair(result1.get(1), "swiss", ch_hello);
-
-        final List<NameValuePair> result2 = URLEncodedUtils.parse(s, StandardCharsets.UTF_8, parameterSeparator);
-        Assert.assertEquals(2, result2.size());
-        assertNameValuePair(result2.get(0), "russian", ru_hello);
-        assertNameValuePair(result2.get(1), "swiss", ch_hello);
-    }
-
-    @Test
-    public void testEmptyQuery() throws Exception {
-        final List<NameValuePair> result = URLEncodedUtils.parse("", StandardCharsets.UTF_8);
-        Assert.assertEquals(0, result.size());
-        // [HTTPCLIENT-1889]:
-        result.add(new BasicNameValuePair("key", "value"));
-    }
-
-    @Test
-    public void testFormat() throws Exception {
-        final List <NameValuePair> params = new ArrayList<>();
-        Assert.assertEquals(0, URLEncodedUtils.format(params, StandardCharsets.US_ASCII).length());
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name0", null));
-        Assert.assertEquals("Name0", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name1", "Value1"));
-        Assert.assertEquals("Name1=Value1", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name2", ""));
-        Assert.assertEquals("Name2=", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name4", "Value 4&"));
-        Assert.assertEquals("Name4=Value%204%26", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name4", "Value+4&"));
-        Assert.assertEquals("Name4=Value%2B4%26", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name4", "Value 4& =4"));
-        Assert.assertEquals("Name4=Value%204%26%20%3D4", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name5", "aaa"));
-        params.add(new BasicNameValuePair("Name6", "bbb"));
-        Assert.assertEquals("Name5=aaa&Name6=bbb", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name7", "aaa"));
-        params.add(new BasicNameValuePair("Name7", "b,b"));
-        params.add(new BasicNameValuePair("Name7", "ccc"));
-        Assert.assertEquals("Name7=aaa&Name7=b%2Cb&Name7=ccc", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-        Assert.assertEquals("Name7=aaa&Name7=b%2Cb&Name7=ccc", URLEncodedUtils.format(params, '&', StandardCharsets.US_ASCII));
-        Assert.assertEquals("Name7=aaa;Name7=b%2Cb;Name7=ccc", URLEncodedUtils.format(params, ';', StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name8", "xx,  yy  ,zz"));
-        Assert.assertEquals("Name8=xx%2C%20%20yy%20%20%2Czz", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-    }
-
-    @Test
-    public void testFormatString() throws Exception { // as above, using String
-        final List <NameValuePair> params = new ArrayList<>();
-        Assert.assertEquals(0, URLEncodedUtils.format(params, StandardCharsets.US_ASCII).length());
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name0", null));
-        Assert.assertEquals("Name0", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name1", "Value1"));
-        Assert.assertEquals("Name1=Value1", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name2", ""));
-        Assert.assertEquals("Name2=", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name4", "Value 4&"));
-        Assert.assertEquals("Name4=Value%204%26", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name4", "Value+4&"));
-        Assert.assertEquals("Name4=Value%2B4%26", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name4", "Value 4& =4"));
-        Assert.assertEquals("Name4=Value%204%26%20%3D4", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name5", "aaa"));
-        params.add(new BasicNameValuePair("Name6", "bbb"));
-        Assert.assertEquals("Name5=aaa&Name6=bbb", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name7", "aaa"));
-        params.add(new BasicNameValuePair("Name7", "b,b"));
-        params.add(new BasicNameValuePair("Name7", "ccc"));
-        Assert.assertEquals("Name7=aaa&Name7=b%2Cb&Name7=ccc", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-
-        params.clear();
-        params.add(new BasicNameValuePair("Name8", "xx,  yy  ,zz"));
-        Assert.assertEquals("Name8=xx%2C%20%20yy%20%20%2Czz", URLEncodedUtils.format(params, StandardCharsets.US_ASCII));
-    }
-
-    private List <NameValuePair> parse (final String params) {
-        return URLEncodedUtils.parse(params, StandardCharsets.UTF_8);
-    }
-
-    private List <NameValuePair> parseString (final String uri) throws Exception {
-        return URLEncodedUtils.parse(new URI("?"+uri), StandardCharsets.UTF_8);
-    }
-
-    private static void assertNameValuePair (
-            final NameValuePair parameter,
-            final String expectedName,
-            final String expectedValue) {
-        Assert.assertEquals(parameter.getName(), expectedName);
-        Assert.assertEquals(parameter.getValue(), expectedValue);
-    }
-
-}
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
new file mode 100644
index 0000000..88d4362
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestWWWFormCodec.java
@@ -0,0 +1,123 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.net;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.NameValuePairListMatcher;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
+public class TestWWWFormCodec {
+
+    private static final String CH_HELLO = "\u0047\u0072\u00FC\u0065\u007A\u0069\u005F\u007A\u00E4\u006D\u00E4";
+    private static final String RU_HELLO = "\u0412\u0441\u0435\u043C\u005F\u043F\u0440\u0438\u0432\u0435\u0442";
+
+    private static List<NameValuePair> parse(final String params) {
+        return WWWFormCodec.parse(params, StandardCharsets.UTF_8);
+    }
+
+    @Test
+    public void testParse() throws Exception {
+        MatcherAssert.assertThat(parse(""), NameValuePairListMatcher.isEmpty());
+        MatcherAssert.assertThat(parse("Name0"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name0", null)));
+        MatcherAssert.assertThat(parse("Name1=Value1"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name1", "Value1")));
+        MatcherAssert.assertThat(parse("Name2="),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name2", "")));
+        MatcherAssert.assertThat(parse(" Name3  "),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name3", null)));
+        MatcherAssert.assertThat(parse("Name4=Value%204%21"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name4", "Value 4!")));
+        MatcherAssert.assertThat(parse("Name4=Value%2B4%21"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name4", "Value+4!")));
+        MatcherAssert.assertThat(parse("Name4=Value%204%21%20%214"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name4", "Value 4! !4")));
+        MatcherAssert.assertThat(parse("Name5=aaa&Name6=bbb"),
+                NameValuePairListMatcher.equalsTo(
+                        new BasicNameValuePair("Name5", "aaa"),
+                        new BasicNameValuePair("Name6", "bbb")));
+        MatcherAssert.assertThat(parse("Name7=aaa&Name7=b%2Cb&Name7=ccc"),
+                NameValuePairListMatcher.equalsTo(
+                        new BasicNameValuePair("Name7", "aaa"),
+                        new BasicNameValuePair("Name7", "b,b"),
+                        new BasicNameValuePair("Name7", "ccc")));
+        MatcherAssert.assertThat(parse("Name8=xx%2C%20%20yy%20%20%2Czz"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name8", "xx,  yy  ,zz")));
+        MatcherAssert.assertThat(parse("price=10%20%E2%82%AC"),
+                NameValuePairListMatcher.equalsTo(new BasicNameValuePair("price", "10 \u20AC")));
+        MatcherAssert.assertThat(parse("a=b\"c&d=e"),
+                NameValuePairListMatcher.equalsTo(
+                        new BasicNameValuePair("a", "b\"c"),
+                        new BasicNameValuePair("d", "e")));
+        MatcherAssert.assertThat(parse("russian=" + PercentCodec.encode(RU_HELLO, StandardCharsets.UTF_8) +
+                        "&swiss=" + PercentCodec.encode(CH_HELLO, StandardCharsets.UTF_8)),
+                NameValuePairListMatcher.equalsTo(
+                        new BasicNameValuePair("russian", RU_HELLO),
+                        new BasicNameValuePair("swiss", CH_HELLO)));
+    }
+
+    private static String format(final NameValuePair... nvps) {
+        return WWWFormCodec.format(Arrays.asList(nvps), StandardCharsets.UTF_8);
+    }
+
+    @Test
+    public void testFormat() throws Exception {
+        MatcherAssert.assertThat(format(new BasicNameValuePair("Name0", null)), CoreMatchers.equalTo("Name0"));
+        MatcherAssert.assertThat(format(new BasicNameValuePair("Name1", "Value1")), CoreMatchers.equalTo("Name1=Value1"));
+        MatcherAssert.assertThat(format(new BasicNameValuePair("Name2", "")), CoreMatchers.equalTo("Name2="));
+        MatcherAssert.assertThat(format(new BasicNameValuePair("Name4", "Value 4&")),
+                CoreMatchers.equalTo("Name4=Value+4%26"));
+        MatcherAssert.assertThat(format(new BasicNameValuePair("Name4", "Value+4&")),
+                CoreMatchers.equalTo("Name4=Value%2B4%26"));
+        MatcherAssert.assertThat(format(new BasicNameValuePair("Name4", "Value 4& =4")),
+                CoreMatchers.equalTo("Name4=Value+4%26+%3D4"));
+        MatcherAssert.assertThat(format(
+                new BasicNameValuePair("Name5", "aaa"),
+                new BasicNameValuePair("Name6", "bbb")), CoreMatchers.equalTo("Name5=aaa&Name6=bbb"));
+        MatcherAssert.assertThat(format(
+                new BasicNameValuePair("Name7", "aaa"),
+                new BasicNameValuePair("Name7", "b,b"),
+                new BasicNameValuePair("Name7", "ccc")
+        ), CoreMatchers.equalTo("Name7=aaa&Name7=b%2Cb&Name7=ccc"));
+        MatcherAssert.assertThat(format(new BasicNameValuePair("Name8", "xx,  yy  ,zz")),
+                CoreMatchers.equalTo("Name8=xx%2C++yy++%2Czz"));
+        MatcherAssert.assertThat(format(
+                new BasicNameValuePair("russian", RU_HELLO),
+                new BasicNameValuePair("swiss", CH_HELLO)),
+                CoreMatchers.equalTo("russian=" + PercentCodec.encode(RU_HELLO, StandardCharsets.UTF_8) +
+                        "&swiss=" + PercentCodec.encode(CH_HELLO, StandardCharsets.UTF_8)));
+    }
+
+}


[httpcomponents-core] 01/18: Upgraded project version to 5.1-alpha1-SNAPSHOT

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 684d3c69bb7c59a6a41c7733887b90c6875ae999
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sun Apr 12 16:32:21 2020 +0200

    Upgraded project version to 5.1-alpha1-SNAPSHOT
---
 httpcore5-h2/pom.xml       | 2 +-
 httpcore5-reactive/pom.xml | 2 +-
 httpcore5-testing/pom.xml  | 2 +-
 httpcore5/pom.xml          | 2 +-
 pom.xml                    | 4 ++--
 5 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/httpcore5-h2/pom.xml b/httpcore5-h2/pom.xml
index 92a558c..5ce7ed5 100644
--- a/httpcore5-h2/pom.xml
+++ b/httpcore5-h2/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.httpcomponents.core5</groupId>
     <artifactId>httpcore5-parent</artifactId>
-    <version>5.0.3-SNAPSHOT</version>
+    <version>5.1-alpha1-SNAPSHOT</version>
   </parent>
   <artifactId>httpcore5-h2</artifactId>
   <name>Apache HttpComponents Core HTTP/2</name>
diff --git a/httpcore5-reactive/pom.xml b/httpcore5-reactive/pom.xml
index f46936f..bb102d4 100644
--- a/httpcore5-reactive/pom.xml
+++ b/httpcore5-reactive/pom.xml
@@ -27,7 +27,7 @@
   <parent>
     <artifactId>httpcore5-parent</artifactId>
     <groupId>org.apache.httpcomponents.core5</groupId>
-    <version>5.0.3-SNAPSHOT</version>
+    <version>5.1-alpha1-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
diff --git a/httpcore5-testing/pom.xml b/httpcore5-testing/pom.xml
index a001d9f..52e05f3 100644
--- a/httpcore5-testing/pom.xml
+++ b/httpcore5-testing/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.httpcomponents.core5</groupId>
     <artifactId>httpcore5-parent</artifactId>
-    <version>5.0.3-SNAPSHOT</version>
+    <version>5.1-alpha1-SNAPSHOT</version>
   </parent>
   <artifactId>httpcore5-testing</artifactId>
   <name>Apache HttpComponents Core Integration Tests</name>
diff --git a/httpcore5/pom.xml b/httpcore5/pom.xml
index a2c0519..d80cd46 100644
--- a/httpcore5/pom.xml
+++ b/httpcore5/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.httpcomponents.core5</groupId>
     <artifactId>httpcore5-parent</artifactId>
-    <version>5.0.3-SNAPSHOT</version>
+    <version>5.1-alpha1-SNAPSHOT</version>
   </parent>
   <artifactId>httpcore5</artifactId>
   <name>Apache HttpComponents Core HTTP/1.1</name>
diff --git a/pom.xml b/pom.xml
index f5637e1..893ab85 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,7 +33,7 @@
   <groupId>org.apache.httpcomponents.core5</groupId>
   <artifactId>httpcore5-parent</artifactId>
   <name>Apache HttpComponents Core Parent</name>
-  <version>5.0.3-SNAPSHOT</version>
+  <version>5.1-alpha1-SNAPSHOT</version>
   <description>Apache HttpComponents Core is a library of components for building HTTP enabled services</description>
   <url>https://hc.apache.org/httpcomponents-core-5.0.x/</url>
   <inceptionYear>2005</inceptionYear>
@@ -48,7 +48,7 @@
     <connection>scm:git:https://gitbox.apache.org/repos/asf/httpcomponents-core.git</connection>
     <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/httpcomponents-core.git</developerConnection>
     <url>https://github.com/apache/httpcomponents-core/tree/${project.scm.tag}</url>
-    <tag>5.0.3-SNAPSHOT</tag>
+    <tag>5.1-alpha1-SNAPSHOT</tag>
   </scm>
 
   <modules>


[httpcomponents-core] 11/18: RFC 3986 conformance: support percent-encoded reserved characters in the host component; host component can be empty

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 33eb3b5a0d9b8992d2dfb6e86cba738fda15c401
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Jul 25 12:47:27 2020 +0200

    RFC 3986 conformance: support percent-encoded reserved characters in the host component; host component can be empty
---
 .../main/java/org/apache/hc/core5/net/URIAuthority.java    | 10 +---------
 .../src/main/java/org/apache/hc/core5/net/URIBuilder.java  | 14 ++++++++++++--
 .../java/org/apache/hc/core5/net/TestURIAuthority.java     | 14 ++++----------
 .../test/java/org/apache/hc/core5/net/TestURIBuilder.java  | 14 ++++++++++++++
 4 files changed, 31 insertions(+), 21 deletions(-)

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 1cae817..920c426 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
@@ -34,7 +34,6 @@ import java.util.Locale;
 
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
-import org.apache.hc.core5.util.Args;
 import org.apache.hc.core5.util.LangUtils;
 import org.apache.hc.core5.util.TextUtils;
 import org.apache.hc.core5.util.Tokenizer;
@@ -104,9 +103,6 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
         } else {
             hostName = token;
         }
-        if (TextUtils.isBlank(hostName)) {
-            throw createException(s, cursor, "Authority host is empty");
-        }
         final int port;
         if (!TextUtils.isBlank(portText)) {
             try {
@@ -162,12 +158,8 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
      */
     public URIAuthority(final String userInfo, final String hostname, final int port) {
         super();
-        Args.containsNoBlanks(hostname, "Host name");
-        if (userInfo != null) {
-            Args.containsNoBlanks(userInfo, "User info");
-        }
         this.userInfo = userInfo;
-        this.hostname = hostname.toLowerCase(Locale.ROOT);
+        this.hostname = hostname != null ? hostname.toLowerCase(Locale.ROOT) : null;
         this.port = Ports.checkWithDefault(port);
     }
 
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 70358e2..c8f3fa7 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
@@ -292,7 +292,7 @@ public class URIBuilder {
                 if (InetAddressUtils.isIPv6Address(this.host)) {
                     sb.append("[").append(this.host).append("]");
                 } else {
-                    sb.append(this.host);
+                    sb.append(PercentCodec.encode(this.host, this.charset));
                 }
                 if (this.port >= 0) {
                     sb.append(":").append(this.port);
@@ -336,6 +336,16 @@ public class URIBuilder {
         this.port = uri.getPort();
         this.encodedUserInfo = uri.getRawUserInfo();
         this.userInfo = uri.getUserInfo();
+        if (this.encodedAuthority != null && this.host == null) {
+            try {
+                final URIAuthority uriAuthority = URIAuthority.parse(this.encodedAuthority);
+                this.encodedUserInfo = uriAuthority.getUserInfo();
+                this.userInfo = PercentCodec.decode(uriAuthority.getUserInfo(), charset);
+                this.host = PercentCodec.decode(uriAuthority.getHostName(), charset);
+                this.port = uriAuthority.getPort();
+            } catch (final URISyntaxException ignore) {
+            }
+        }
         this.encodedPath = uri.getRawPath();
         this.pathSegments = parsePath(uri.getRawPath(), charset);
         this.pathRootless = uri.getRawPath() != null && !uri.getRawPath().startsWith("/");
@@ -447,7 +457,7 @@ public class URIBuilder {
      * @return this.
      */
     public URIBuilder setHost(final String host) {
-        this.host = !TextUtils.isBlank(host) ? host : null;
+        this.host = host;
         this.encodedSchemeSpecificPart = null;
         this.encodedAuthority = null;
         return this;
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 76c3b41..3aa1986 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
@@ -129,16 +129,10 @@ public class TestURIAuthority {
                 CoreMatchers.equalTo(new URIAuthority("somehost", -1)));
         MatcherAssert.assertThat(URIAuthority.parse("somehost#blah"),
                 CoreMatchers.equalTo(new URIAuthority("somehost", -1)));
-        try {
-            URIAuthority.create("aaaa@:8080");
-            Assert.fail("URISyntaxException expected");
-        } catch (final URISyntaxException expected) {
-        }
-        try {
-            URIAuthority.create("@:");
-            Assert.fail("URISyntaxException expected");
-        } catch (final URISyntaxException expected) {
-        }
+        MatcherAssert.assertThat(URIAuthority.parse("aaaa@:8080"),
+                CoreMatchers.equalTo(new URIAuthority("aaaa", "", 8080)));
+        MatcherAssert.assertThat(URIAuthority.parse("@:"),
+                CoreMatchers.equalTo(new URIAuthority(null, "", -1)));
         MatcherAssert.assertThat(URIAuthority.parse("somehost:8080"),
                 CoreMatchers.equalTo(new URIAuthority("somehost", 8080)));
         MatcherAssert.assertThat(URIAuthority.parse("somehost:8080/blah"),
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 ea153cf..8e5b334 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
@@ -512,6 +512,20 @@ public class TestURIBuilder {
     }
 
     @Test
+    public void testSetHostWithReservedChars() throws Exception {
+        final URIBuilder uribuilder = new URIBuilder();
+        uribuilder.setScheme("http").setHost("!example!.com");
+        Assert.assertEquals(URI.create("http://%21example%21.com"), uribuilder.build());
+    }
+
+    @Test
+    public void testGetHostWithReservedChars() throws Exception {
+        final URIBuilder uribuilder = new URIBuilder("http://someuser%21@%21example%21.com/");
+        Assert.assertEquals("!example!.com", uribuilder.getHost());
+        Assert.assertEquals("someuser!", uribuilder.getUserInfo());
+    }
+
+    @Test
     public void testMultipleLeadingPathSlashes() throws Exception {
         final URI uri = new URIBuilder()
                 .setScheme("ftp")


[httpcomponents-core] 17/18: HTTPCORE-649: Implement ByteArrayBuffer.append(ByteBuffer)

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit f9374c0bf17a06a8fa1113c3357424f005022e6f
Author: Carter Kozak <ck...@ckozak.net>
AuthorDate: Thu Aug 27 13:51:02 2020 -0400

    HTTPCORE-649: Implement ByteArrayBuffer.append(ByteBuffer)
---
 .../apache/hc/core5/http2/hpack/HPackDecoder.java  | 18 ++---
 .../apache/hc/core5/http2/hpack/HPackEncoder.java  | 11 +---
 .../http/nio/entity/BasicAsyncEntityConsumer.java  | 11 +---
 .../org/apache/hc/core5/util/ByteArrayBuffer.java  | 16 +++++
 .../entity/TestAbstractBinAsyncEntityConsumer.java | 11 +---
 .../apache/hc/core5/util/TestByteArrayBuffer.java  | 76 ++++++++++++++++++++++
 6 files changed, 101 insertions(+), 42 deletions(-)

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 dd9379c..1df29a2 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
@@ -125,21 +125,15 @@ public final class HPackDecoder {
     }
 
     static void decodePlainString(final ByteArrayBuffer buffer, final ByteBuffer src) throws HPackException {
-
         final int strLen = decodeInt(src, 7);
-        if (strLen > src.remaining()) {
+        final int remaining = src.remaining();
+        if (strLen > remaining) {
             throw new HPackException(UNEXPECTED_EOS);
         }
-        if (src.hasArray()) {
-            final byte[] b = src.array();
-            final int off = src.position();
-            buffer.append(b, src.arrayOffset() + off, strLen);
-            src.position(off + strLen);
-        } else {
-            for (int i = 0; i < strLen; i++) {
-                buffer.append(src.get());
-            }
-        }
+        final int originalLimit = src.limit();
+        src.limit(originalLimit - (remaining - strLen));
+        buffer.append(src);
+        src.limit(originalLimit);
     }
 
     static void decodeHuffman(final ByteArrayBuffer buffer, final ByteBuffer src) throws HPackException {
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackEncoder.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackEncoder.java
index 2824760..621bdf4 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackEncoder.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackEncoder.java
@@ -110,16 +110,7 @@ public final class HPackEncoder {
         } else {
             dst.ensureCapacity(strLen + 8);
             encodeInt(dst, 7, strLen, 0x0);
-            if (src.hasArray()) {
-                final byte[] b = src.array();
-                final int off = src.position();
-                dst.append(b, src.arrayOffset() + off, strLen);
-                src.position(off + strLen);
-            } else {
-                while (src.hasRemaining()) {
-                    dst.append(src.get());
-                }
-            }
+            dst.append(src);
         }
     }
 
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/BasicAsyncEntityConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/BasicAsyncEntityConsumer.java
index 26487ec..648bde6 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/BasicAsyncEntityConsumer.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/BasicAsyncEntityConsumer.java
@@ -60,16 +60,7 @@ public class BasicAsyncEntityConsumer extends AbstractBinAsyncEntityConsumer<byt
 
     @Override
     protected void data(final ByteBuffer src, final boolean endOfStream) throws IOException {
-        if (src == null) {
-            return;
-        }
-        if (src.hasArray()) {
-            buffer.append(src.array(), src.arrayOffset() + src.position(), src.remaining());
-        } else {
-            while (src.hasRemaining()) {
-                buffer.append(src.get());
-            }
-        }
+        buffer.append(src);
     }
 
     @Override
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/util/ByteArrayBuffer.java b/httpcore5/src/main/java/org/apache/hc/core5/util/ByteArrayBuffer.java
index 573faa5..c9479c3 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/util/ByteArrayBuffer.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/util/ByteArrayBuffer.java
@@ -28,6 +28,7 @@
 package org.apache.hc.core5.util;
 
 import java.io.Serializable;
+import java.nio.ByteBuffer;
 
 /**
  * A resizable byte array.
@@ -171,6 +172,21 @@ public final class ByteArrayBuffer implements Serializable {
         append(b.array(), off, len);
     }
 
+    public void append(final ByteBuffer buffer) {
+        if (buffer == null) {
+            return;
+        }
+        final int bufferLength = buffer.remaining();
+        if (bufferLength > 0) {
+            final int newLength = this.len + bufferLength;
+            if (newLength > this.array.length) {
+                expand(newLength);
+            }
+            buffer.get(this.array, this.len, bufferLength);
+            this.len = newLength;
+        }
+    }
+
     /**
      * Clears content of the buffer. The underlying byte array is not resized.
      */
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 7c7b12a..ea7609e 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
@@ -62,16 +62,7 @@ public class TestAbstractBinAsyncEntityConsumer {
 
         @Override
         protected void data(final ByteBuffer src, final boolean endOfStream) throws IOException {
-            if (src == null) {
-                return;
-            }
-            if (src.hasArray()) {
-                buffer.append(src.array(), src.arrayOffset() + src.position(), src.remaining());
-            } else {
-                while (src.hasRemaining()) {
-                    buffer.append(src.get());
-                }
-            }
+            buffer.append(src);
         }
 
         @Override
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 204426a..6d8d6f5 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
@@ -31,6 +31,7 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 
 import org.junit.Assert;
@@ -109,6 +110,73 @@ public class TestByteArrayBuffer {
     }
 
     @Test
+    public void testAppendHeapByteBuffer() {
+        final ByteArrayBuffer buffer = new ByteArrayBuffer(4);
+        Assert.assertEquals(4, buffer.capacity());
+
+        final ByteBuffer tmp = ByteBuffer.wrap(new byte[] { 1, 2, 3, 4, 5, 6});
+        buffer.append(tmp);
+
+        Assert.assertFalse("The input buffer should be drained", tmp.hasRemaining());
+        Assert.assertEquals(8, buffer.capacity());
+        Assert.assertEquals(6, buffer.length());
+
+        tmp.clear();
+        buffer.append(tmp);
+
+        Assert.assertFalse("The input buffer should be drained", tmp.hasRemaining());
+        Assert.assertEquals(16, buffer.capacity());
+        Assert.assertEquals(12, buffer.length());
+        Assert.assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6}, buffer.toByteArray());
+    }
+
+    @Test
+    public void testAppendHeapByteBufferWithOffset() {
+        final ByteArrayBuffer buffer = new ByteArrayBuffer(4);
+        Assert.assertEquals(4, buffer.capacity());
+
+        final ByteBuffer tmp = ByteBuffer.wrap(new byte[] { 7, 7, 1, 2, 3, 4, 5, 6, 7, 7}, 2, 6).slice();
+        Assert.assertTrue("Validate this is testing a buffer with an array offset", tmp.arrayOffset() > 0);
+
+        buffer.append(tmp);
+
+        Assert.assertFalse("The input buffer should be drained", tmp.hasRemaining());
+        Assert.assertEquals(8, buffer.capacity());
+        Assert.assertEquals(6, buffer.length());
+
+        tmp.clear();
+        Assert.assertEquals(6, tmp.remaining());
+        buffer.append(tmp);
+
+        Assert.assertFalse("The input buffer should be drained", tmp.hasRemaining());
+        Assert.assertEquals(16, buffer.capacity());
+        Assert.assertEquals(12, buffer.length());
+        Assert.assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6}, buffer.toByteArray());
+    }
+
+    @Test
+    public void testAppendDirectByteBuffer() {
+        final ByteArrayBuffer buffer = new ByteArrayBuffer(4);
+        Assert.assertEquals(4, buffer.capacity());
+
+        final ByteBuffer tmp = ByteBuffer.allocateDirect(6);
+        tmp.put(new byte[] { 1, 2, 3, 4, 5, 6}).flip();
+        buffer.append(tmp);
+
+        Assert.assertFalse("The input buffer should be drained", tmp.hasRemaining());
+        Assert.assertEquals(8, buffer.capacity());
+        Assert.assertEquals(6, buffer.length());
+
+        tmp.clear();
+        buffer.append(tmp);
+
+        Assert.assertFalse("The input buffer should be drained", tmp.hasRemaining());
+        Assert.assertEquals(16, buffer.capacity());
+        Assert.assertEquals(12, buffer.length());
+        Assert.assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6}, buffer.toByteArray());
+    }
+
+    @Test
     public void testInvalidAppend() throws Exception {
         final ByteArrayBuffer buffer = new ByteArrayBuffer(4);
         buffer.append((byte[])null, 0, 0);
@@ -251,6 +319,14 @@ public class TestByteArrayBuffer {
     }
 
     @Test
+    public void testAppendNullByteBuffer() throws Exception {
+        final ByteArrayBuffer buffer = new ByteArrayBuffer(8);
+        final ByteBuffer nullBuffer = null;
+        buffer.append(nullBuffer);
+        Assert.assertEquals(0, buffer.length());
+    }
+
+    @Test
     public void testInvalidAppendCharArrayAsAscii() throws Exception {
         final ByteArrayBuffer buffer = new ByteArrayBuffer(4);
         buffer.append((char[])null, 0, 0);


[httpcomponents-core] 18/18: Updated project URL

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit d05d6a914effbc761c6cea0fbc4d067ecd4f342c
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Sep 12 14:33:27 2020 +0200

    Updated project URL
---
 httpcore5-h2/pom.xml       | 2 +-
 httpcore5-reactive/pom.xml | 2 +-
 httpcore5-testing/pom.xml  | 2 +-
 httpcore5/pom.xml          | 2 +-
 pom.xml                    | 4 ++--
 5 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/httpcore5-h2/pom.xml b/httpcore5-h2/pom.xml
index 5ce7ed5..004d5e2 100644
--- a/httpcore5-h2/pom.xml
+++ b/httpcore5-h2/pom.xml
@@ -33,7 +33,7 @@
   <artifactId>httpcore5-h2</artifactId>
   <name>Apache HttpComponents Core HTTP/2</name>
   <description>Apache HttpComponents HTTP/2 Core Components</description>
-  <url>https://hc.apache.org/httpcomponents-core-5.0.x/</url>
+  <url>https://hc.apache.org/httpcomponents-core-5.1.x/</url>
   <packaging>jar</packaging>
 
   <properties>
diff --git a/httpcore5-reactive/pom.xml b/httpcore5-reactive/pom.xml
index bb102d4..4fe7aa4 100644
--- a/httpcore5-reactive/pom.xml
+++ b/httpcore5-reactive/pom.xml
@@ -34,7 +34,7 @@
   <artifactId>httpcore5-reactive</artifactId>
   <name>Apache HttpComponents Core Reactive Extensions</name>
   <description>Apache HttpComponents Reactive Streams Bindings</description>
-  <url>https://hc.apache.org/httpcomponents-core-5.0.x/</url>
+  <url>https://hc.apache.org/httpcomponents-core-5.1.x/</url>
   <packaging>jar</packaging>
 
   <properties>
diff --git a/httpcore5-testing/pom.xml b/httpcore5-testing/pom.xml
index 52e05f3..2d11b5a 100644
--- a/httpcore5-testing/pom.xml
+++ b/httpcore5-testing/pom.xml
@@ -33,7 +33,7 @@
   <artifactId>httpcore5-testing</artifactId>
   <name>Apache HttpComponents Core Integration Tests</name>
   <description>Apache HttpComponents HTTP/2 and HTTP/1.1 core component integration tests</description>
-  <url>https://hc.apache.org/httpcomponents-core-5.0.x/</url>
+  <url>https://hc.apache.org/httpcomponents-core-5.1.x/</url>
   <packaging>jar</packaging>
 
   <properties>
diff --git a/httpcore5/pom.xml b/httpcore5/pom.xml
index d80cd46..3cfe0b9 100644
--- a/httpcore5/pom.xml
+++ b/httpcore5/pom.xml
@@ -34,7 +34,7 @@
   <name>Apache HttpComponents Core HTTP/1.1</name>
   <inceptionYear>2005</inceptionYear>
   <description>Apache HttpComponents HTTP/1.1 core components</description>
-  <url>https://hc.apache.org/httpcomponents-core-5.0.x/</url>
+  <url>https://hc.apache.org/httpcomponents-core-5.1.x/</url>
   <packaging>jar</packaging>
 
   <properties>
diff --git a/pom.xml b/pom.xml
index 893ab85..414c99b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,7 +35,7 @@
   <name>Apache HttpComponents Core Parent</name>
   <version>5.1-alpha1-SNAPSHOT</version>
   <description>Apache HttpComponents Core is a library of components for building HTTP enabled services</description>
-  <url>https://hc.apache.org/httpcomponents-core-5.0.x/</url>
+  <url>https://hc.apache.org/httpcomponents-core-5.1.x/</url>
   <inceptionYear>2005</inceptionYear>
   <packaging>pom</packaging>
 
@@ -48,7 +48,7 @@
     <connection>scm:git:https://gitbox.apache.org/repos/asf/httpcomponents-core.git</connection>
     <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/httpcomponents-core.git</developerConnection>
     <url>https://github.com/apache/httpcomponents-core/tree/${project.scm.tag}</url>
-    <tag>5.1-alpha1-SNAPSHOT</tag>
+    <tag>master</tag>
   </scm>
 
   <modules>


[httpcomponents-core] 06/18: HTTPCORE-627: Adding query parameters causes removal of the scheme specific part of URI by URIBuilder (#196)

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 51ff9cbec24cdcbe7bcee3ba1f10fa19f1b5b809
Author: TorstenR <ne...@users.noreply.github.com>
AuthorDate: Tue Apr 21 09:04:46 2020 +0200

    HTTPCORE-627: Adding query parameters causes removal of the scheme specific part of URI by URIBuilder (#196)
---
 .../java/org/apache/hc/core5/net/URIBuilder.java   | 55 ++++++++++++++++++++++
 .../org/apache/hc/core5/net/TestURIBuilder.java    | 34 +++++++++++++
 2 files changed, 89 insertions(+)

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 9863d6e..3fbe2d7 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
@@ -264,6 +264,51 @@ public class URIBuilder {
     }
 
     /**
+     * Sets the URI scheme specific part.
+     *
+     * @param schemeSpecificPart
+     * @return this.
+     * @since 5.1
+     */
+    public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart) {
+        this.encodedSchemeSpecificPart = schemeSpecificPart;
+        return this;
+    }
+
+    /**
+     * Sets the URI scheme specific part and append a variable arguments list of NameValuePair instance(s) to this part.
+     *
+     * @param schemeSpecificPart
+     * @param nvps Optional, can be null. Variable arguments list of NameValuePair query parameters to be reused by the specific scheme part
+     * @return this.
+     * @since 5.1
+     */
+    public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart, final NameValuePair... nvps) {
+        return setSchemeSpecificPart(schemeSpecificPart, nvps != null ? Arrays.asList(nvps) : null);
+    }
+
+    /**
+     * Sets the URI scheme specific part and append a list of NameValuePair to this part.
+     *
+     * @param schemeSpecificPart
+     * @param nvps Optional, can be null. List of query parameters to be reused by the specific scheme part
+     * @return this.
+     * @since 5.1
+     */
+    public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart, final List <NameValuePair> nvps) {
+        this.encodedSchemeSpecificPart = null;
+        if (!TextUtils.isBlank(schemeSpecificPart)) {
+            final StringBuilder sb = new StringBuilder(schemeSpecificPart);
+            if (nvps != null && !nvps.isEmpty()) {
+                sb.append("?");
+                encodeUrlForm(sb, nvps);
+            }
+            this.encodedSchemeSpecificPart = sb.toString();
+        }
+        return this;
+    }
+
+    /**
      * Sets URI user info. The value is expected to be unescaped and may contain non ASCII
      * characters.
      *
@@ -555,6 +600,16 @@ public class URIBuilder {
         return this.scheme;
     }
 
+    /**
+     * Gets the scheme specific part
+     *
+     * @return String
+     * @since 5.1
+     */
+    public String getSchemeSpecificPart() {
+        return this.encodedSchemeSpecificPart;
+    }
+
     public String getUserInfo() {
         return this.userInfo;
     }
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 cf68126..f86d345 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
@@ -464,4 +464,38 @@ public class TestURIBuilder {
         Assert.assertEquals(uri, result);
     }
 
+    @Test
+    public void testSchemeSpecificPartParametersNull() throws Exception {
+       final URIBuilder uribuilder = new URIBuilder("http://host.com").setParameter("par", "parvalue")
+               .setSchemeSpecificPart("", (NameValuePair)null);
+       Assert.assertEquals(new URI("http://host.com?par=parvalue"), uribuilder.build());
+    }
+
+    @Test
+    public void testSchemeSpecificPartSetGet() throws Exception {
+       final URIBuilder uribuilder = new URIBuilder().setSchemeSpecificPart("specificpart");
+       Assert.assertEquals("specificpart", uribuilder.getSchemeSpecificPart());
+    }
+
+    /** Common use case: mailto: scheme. See https://tools.ietf.org/html/rfc6068#section-2 */
+    @Test
+    public 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();
+       Assert.assertTrue("mail address as scheme specific part expected", result.contains("my@email.server"));
+       Assert.assertTrue("correct parameter encoding expected for that scheme", result.contains("mail%20subject"));
+    }
+
+    /** Common use case: mailto: scheme. See https://tools.ietf.org/html/rfc6068#section-2 */
+    @Test
+    public void testSchemeSpecificPartNameValuePairListByRFC6068Sample() throws Exception {
+        final List<NameValuePair> parameters = new ArrayList<>();
+        parameters.add(new BasicNameValuePair("subject", "mail subject"));
+
+       final URIBuilder uribuilder = new URIBuilder().setScheme("mailto").setSchemeSpecificPart("my@email.server", parameters);
+       final String result = uribuilder.build().toString();
+       Assert.assertTrue("mail address as scheme specific part expected", result.contains("my@email.server"));
+       Assert.assertTrue("correct parameter encoding expected for that scheme", result.contains("mail%20subject"));
+    }
 }


[httpcomponents-core] 02/18: Async connection listeners to support passing attachments to endpoints

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 4e52bcf62fd35527e3bbce3db34a4f131d729905
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Fri Apr 10 15:39:30 2020 +0200

    Async connection listeners to support passing attachments to endpoints
---
 .../hc/core5/http/impl/bootstrap/AsyncServer.java  | 12 +++++-
 ...tenerEndpointRequest.java => ChannelEntry.java} | 47 ++++++----------------
 .../hc/core5/reactor/ConnectionAcceptor.java       |  3 ++
 ...ectionAcceptor.java => ConnectionListener.java} |  7 ++--
 .../core5/reactor/DefaultListeningIOReactor.java   | 23 ++++++-----
 .../hc/core5/reactor/ListenerEndpointImpl.java     |  6 ++-
 .../hc/core5/reactor/ListenerEndpointRequest.java  |  4 +-
 .../hc/core5/reactor/SingleCoreIOReactor.java      | 20 ++++-----
 .../reactor/SingleCoreListeningIOReactor.java      | 33 +++++++++------
 9 files changed, 82 insertions(+), 73 deletions(-)

diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServer.java
index 480b592..8a5c040 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServer.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServer.java
@@ -40,6 +40,7 @@ import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.io.CloseMode;
 import org.apache.hc.core5.reactor.ConnectionAcceptor;
 import org.apache.hc.core5.reactor.ConnectionInitiator;
+import org.apache.hc.core5.reactor.ConnectionListener;
 import org.apache.hc.core5.reactor.DefaultListeningIOReactor;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
 import org.apache.hc.core5.reactor.IOReactorConfig;
@@ -53,7 +54,8 @@ import org.apache.hc.core5.util.TimeValue;
 /**
  * Protocol agnostic server side I/O session handler.
  */
-public class AsyncServer extends AbstractConnectionInitiatorBase implements IOReactorService, ConnectionAcceptor {
+public class AsyncServer extends AbstractConnectionInitiatorBase
+        implements IOReactorService, ConnectionListener, ConnectionAcceptor {
 
     private final DefaultListeningIOReactor ioReactor;
 
@@ -87,8 +89,14 @@ public class AsyncServer extends AbstractConnectionInitiatorBase implements IORe
     }
 
     @Override
+    public Future<ListenerEndpoint> listen(
+            final SocketAddress address, final Object attachment, final FutureCallback<ListenerEndpoint> callback) {
+        return ioReactor.listen(address, attachment, callback);
+    }
+
+    @Override
     public Future<ListenerEndpoint> listen(final SocketAddress address, final FutureCallback<ListenerEndpoint> callback) {
-        return ioReactor.listen(address, callback);
+        return listen(address, null, callback);
     }
 
     public Future<ListenerEndpoint> listen(final SocketAddress address) {
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointRequest.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ChannelEntry.java
similarity index 57%
copy from httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointRequest.java
copy to httpcore5/src/main/java/org/apache/hc/core5/reactor/ChannelEntry.java
index 53b0c16..9451bf84 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointRequest.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ChannelEntry.java
@@ -27,46 +27,25 @@
 
 package org.apache.hc.core5.reactor;
 
-import java.io.Closeable;
-import java.net.SocketAddress;
+import java.nio.channels.SocketChannel;
 
-import org.apache.hc.core5.concurrent.BasicFuture;
+final class ChannelEntry {
 
-final class ListenerEndpointRequest implements Closeable {
+    final SocketChannel channel;
+    final Object attachment;
 
-    final SocketAddress address;
-    final BasicFuture<ListenerEndpoint> future;
-
-    ListenerEndpointRequest(final SocketAddress address, final BasicFuture<ListenerEndpoint> future) {
-        this.address = address;
-        this.future = future;
-    }
-
-    public void completed(final ListenerEndpoint endpoint) {
-        if (future != null) {
-            future.completed(endpoint);
-        }
-    }
-
-    public void failed(final Exception cause) {
-        if (future != null) {
-            future.failed(cause);
-        }
-    }
-
-    public void cancel() {
-        if (future != null) {
-            future.cancel();
-        }
-    }
-
-    public boolean isCancelled() {
-        return future != null && future.isCancelled();
+    public ChannelEntry(final SocketChannel channel, final Object attachment) {
+        super();
+        this.channel = channel;
+        this.attachment = attachment;
     }
 
     @Override
-    public void close() {
-        cancel();
+    public String toString() {
+        return "[" +
+                "channel=" + channel +
+                ", attachment=" + attachment +
+                ']';
     }
 
 }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ConnectionAcceptor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ConnectionAcceptor.java
index e98eb61..490287b 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ConnectionAcceptor.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ConnectionAcceptor.java
@@ -38,7 +38,10 @@ import org.apache.hc.core5.concurrent.FutureCallback;
  * Non-blocking connection acceptor.
  *
  * @since 5.0
+ *
+ * @deprecated Use {@link ConnectionListener}
  */
+@Deprecated
 public interface ConnectionAcceptor {
 
     /**
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ConnectionAcceptor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ConnectionListener.java
similarity index 91%
copy from httpcore5/src/main/java/org/apache/hc/core5/reactor/ConnectionAcceptor.java
copy to httpcore5/src/main/java/org/apache/hc/core5/reactor/ConnectionListener.java
index e98eb61..a70ac50 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ConnectionAcceptor.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ConnectionListener.java
@@ -37,9 +37,9 @@ import org.apache.hc.core5.concurrent.FutureCallback;
 /**
  * Non-blocking connection acceptor.
  *
- * @since 5.0
+ * @since 5.1
  */
-public interface ConnectionAcceptor {
+public interface ConnectionListener {
 
     /**
      * Opens a new listener endpoint with the given socket address. Once
@@ -48,10 +48,11 @@ public interface ConnectionAcceptor {
      * dispatcher.
      *
      * @param address the socket address to listen on.
+     * @param attachment the attachment object.
      * @param callback the result callback.
      * @return listener endpoint.
      */
-    Future<ListenerEndpoint> listen(SocketAddress address, FutureCallback<ListenerEndpoint> callback);
+    Future<ListenerEndpoint> listen(SocketAddress address, Object attachment, FutureCallback<ListenerEndpoint> callback);
 
     /**
      * Suspends the I/O reactor preventing it from accepting new connections on
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultListeningIOReactor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultListeningIOReactor.java
index 0a9a84c..b3b6a6a 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultListeningIOReactor.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultListeningIOReactor.java
@@ -29,7 +29,6 @@ package org.apache.hc.core5.reactor;
 
 import java.io.IOException;
 import java.net.SocketAddress;
-import java.nio.channels.SocketChannel;
 import java.util.Set;
 import java.util.concurrent.Future;
 import java.util.concurrent.ThreadFactory;
@@ -44,14 +43,14 @@ import org.apache.hc.core5.util.TimeValue;
 
 /**
  * Multi-core I/O reactor that can ask as both {@link ConnectionInitiator}
- * and {@link ConnectionAcceptor}. Internally this I/O reactor distributes newly created
+ * and {@link ConnectionListener}. Internally this I/O reactor distributes newly created
  * I/O session equally across multiple I/O worker threads for a more optimal resource
  * utilization and a better I/O performance. Usually it is recommended to have
  * one worker I/O reactor per physical CPU core.
  *
  * @since 4.0
  */
-public class DefaultListeningIOReactor extends AbstractIOReactorBase implements ConnectionAcceptor {
+public class DefaultListeningIOReactor extends AbstractIOReactorBase implements ConnectionListener, ConnectionAcceptor {
 
     private final static ThreadFactory DISPATCH_THREAD_FACTORY = new DefaultThreadFactory("I/O server dispatch", true);
     private final static ThreadFactory LISTENER_THREAD_FACTORY = new DefaultThreadFactory("I/O listener", true);
@@ -98,11 +97,11 @@ public class DefaultListeningIOReactor extends AbstractIOReactorBase implements
         }
         final IOReactor[] ioReactors = new IOReactor[this.workerCount + 1];
         System.arraycopy(this.workers, 0, ioReactors, 1, this.workerCount);
-        this.listener = new SingleCoreListeningIOReactor(exceptionCallback, ioReactorConfig, new Callback<SocketChannel>() {
+        this.listener = new SingleCoreListeningIOReactor(exceptionCallback, ioReactorConfig, new Callback<ChannelEntry>() {
 
             @Override
-            public void execute(final SocketChannel channel) {
-                enqueueChannel(channel);
+            public void execute(final ChannelEntry entry) {
+                enqueueChannel(entry);
             }
 
         });
@@ -147,8 +146,14 @@ public class DefaultListeningIOReactor extends AbstractIOReactorBase implements
     }
 
     @Override
+    public Future<ListenerEndpoint> listen(
+            final SocketAddress address, final Object attachment, final FutureCallback<ListenerEndpoint> callback) {
+        return listener.listen(address, attachment, callback);
+    }
+
+    @Override
     public Future<ListenerEndpoint> listen(final SocketAddress address, final FutureCallback<ListenerEndpoint> callback) {
-        return listener.listen(address, callback);
+        return listen(address, null, callback);
     }
 
     public Future<ListenerEndpoint> listen(final SocketAddress address) {
@@ -180,9 +185,9 @@ public class DefaultListeningIOReactor extends AbstractIOReactorBase implements
         return workerSelector;
     }
 
-    private void enqueueChannel(final SocketChannel socketChannel) {
+    private void enqueueChannel(final ChannelEntry entry) {
         try {
-            workerSelector.next().enqueueChannel(socketChannel);
+            workerSelector.next().enqueueChannel(entry);
         } catch (final IOReactorShutdownException ex) {
             initiateShutdown();
         }
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 e16be0d..f5ac698 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
@@ -38,13 +38,15 @@ import org.apache.hc.core5.io.Closer;
 class ListenerEndpointImpl implements ListenerEndpoint {
 
     private final SelectionKey key;
-    private final SocketAddress address;
+    final SocketAddress address;
+    final Object attachment;
     private final AtomicBoolean closed;
 
-    public ListenerEndpointImpl(final SelectionKey key, final SocketAddress address) {
+    public ListenerEndpointImpl(final SelectionKey key, final Object attachment, final SocketAddress address) {
         super();
         this.key = key;
         this.address = address;
+        this.attachment = attachment;
         this.closed = new AtomicBoolean(false);
     }
 
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointRequest.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointRequest.java
index 53b0c16..faebb22 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointRequest.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointRequest.java
@@ -35,10 +35,12 @@ import org.apache.hc.core5.concurrent.BasicFuture;
 final class ListenerEndpointRequest implements Closeable {
 
     final SocketAddress address;
+    final Object attachment;
     final BasicFuture<ListenerEndpoint> future;
 
-    ListenerEndpointRequest(final SocketAddress address, final BasicFuture<ListenerEndpoint> future) {
+    ListenerEndpointRequest(final SocketAddress address, final Object attachment, final BasicFuture<ListenerEndpoint> future) {
         this.address = address;
+        this.attachment = attachment;
         this.future = future;
     }
 
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 f986030..b691f3d 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
@@ -65,7 +65,7 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect
     private final IOSessionListener sessionListener;
     private final Callback<IOSession> sessionShutdownCallback;
     private final Queue<InternalDataChannel> closedSessions;
-    private final Queue<SocketChannel> channelQueue;
+    private final Queue<ChannelEntry> channelQueue;
     private final Queue<IOSessionRequest> requestQueue;
     private final AtomicBoolean shutdownInitiated;
     private final long selectTimeoutMillis;
@@ -91,12 +91,11 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect
         this.selectTimeoutMillis = this.reactorConfig.getSelectInterval().toMilliseconds();
     }
 
-    void enqueueChannel(final SocketChannel socketChannel) throws IOReactorShutdownException {
-        Args.notNull(socketChannel, "SocketChannel");
+    void enqueueChannel(final ChannelEntry entry) throws IOReactorShutdownException {
         if (getStatus().compareTo(IOReactorStatus.ACTIVE) > 0) {
             throw new IOReactorShutdownException("I/O reactor has been shut down");
         }
-        this.channelQueue.add(socketChannel);
+        this.channelQueue.add(entry);
         this.selector.wakeup();
     }
 
@@ -186,8 +185,10 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect
     }
 
     private void processPendingChannels() throws IOException {
-        SocketChannel socketChannel;
-        for (int i = 0; i < MAX_CHANNEL_REQUESTS && (socketChannel = this.channelQueue.poll()) != null; i++) {
+        ChannelEntry entry;
+        for (int i = 0; i < MAX_CHANNEL_REQUESTS && (entry = this.channelQueue.poll()) != null; i++) {
+            final SocketChannel socketChannel = entry.channel;
+            final Object attachment = entry.attachment;
             try {
                 prepareSocket(socketChannel.socket());
                 socketChannel.configureBlocking(false);
@@ -212,7 +213,7 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect
                     null,
                     sessionListener,
                     closedSessions);
-            dataChannel.upgrade(this.eventHandlerFactory.createHandler(dataChannel, null));
+            dataChannel.upgrade(this.eventHandlerFactory.createHandler(dataChannel, attachment));
             dataChannel.setSocketTimeout(this.reactorConfig.getSoTimeout());
             key.attach(dataChannel);
             dataChannel.handleIOEvent(SelectionKey.OP_CONNECT);
@@ -384,8 +385,9 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect
     }
 
     private void closePendingChannels() {
-        SocketChannel socketChannel;
-        while ((socketChannel = this.channelQueue.poll()) != null) {
+        ChannelEntry entry;
+        while ((entry = this.channelQueue.poll()) != null) {
+            final SocketChannel socketChannel = entry.channel;
             try {
                 socketChannel.close();
             } catch (final IOException ex) {
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 dff280a..d3603b8 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
@@ -50,19 +50,19 @@ import org.apache.hc.core5.concurrent.FutureCallback;
 import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.io.Closer;
 
-class SingleCoreListeningIOReactor extends AbstractSingleCoreIOReactor implements ConnectionAcceptor {
+class SingleCoreListeningIOReactor extends AbstractSingleCoreIOReactor implements ConnectionListener, ConnectionAcceptor {
 
     private final IOReactorConfig reactorConfig;
-    private final Callback<SocketChannel> callback;
+    private final Callback<ChannelEntry> callback;
     private final Queue<ListenerEndpointRequest> requestQueue;
-    private final ConcurrentMap<ListenerEndpoint, Boolean> endpoints;
+    private final ConcurrentMap<ListenerEndpointImpl, Boolean> endpoints;
     private final AtomicBoolean paused;
     private final long selectTimeoutMillis;
 
     SingleCoreListeningIOReactor(
             final Callback<Exception> exceptionCallback,
             final IOReactorConfig ioReactorConfig,
-            final Callback<SocketChannel> callback) {
+            final Callback<ChannelEntry> callback) {
         super(exceptionCallback);
         this.reactorConfig = ioReactorConfig != null ? ioReactorConfig : IOReactorConfig.DEFAULT;
         this.callback = callback;
@@ -124,28 +124,35 @@ class SingleCoreListeningIOReactor extends AbstractSingleCoreIOReactor implement
                     if (socketChannel == null) {
                         break;
                     }
-                    this.callback.execute(socketChannel);
+                    final ListenerEndpointRequest endpointRequest = (ListenerEndpointRequest) key.attachment();
+                    this.callback.execute(new ChannelEntry(socketChannel, endpointRequest.attachment));
                 }
             }
 
         } catch (final CancelledKeyException ex) {
-            final ListenerEndpoint endpoint = (ListenerEndpoint) key.attachment();
+            final ListenerEndpointImpl endpoint = (ListenerEndpointImpl) key.attachment();
             this.endpoints.remove(endpoint);
             key.attach(null);
         }
     }
 
     @Override
-    public Future<ListenerEndpoint> listen(final SocketAddress address, final FutureCallback<ListenerEndpoint> callback) {
+    public Future<ListenerEndpoint> listen(
+            final SocketAddress address, final Object attachment, final FutureCallback<ListenerEndpoint> callback) {
         if (getStatus().compareTo(IOReactorStatus.SHUTTING_DOWN) >= 0) {
             throw new IOReactorShutdownException("I/O reactor has been shut down");
         }
         final BasicFuture<ListenerEndpoint> future = new BasicFuture<>(callback);
-        this.requestQueue.add(new ListenerEndpointRequest(address, future));
+        this.requestQueue.add(new ListenerEndpointRequest(address, attachment, future));
         this.selector.wakeup();
         return future;
     }
 
+    @Override
+    public Future<ListenerEndpoint> listen(final SocketAddress address, final FutureCallback<ListenerEndpoint> callback) {
+        return listen(address, null, callback);
+    }
+
     private void processSessionRequests() throws IOException {
         ListenerEndpointRequest request;
         while ((request = this.requestQueue.poll()) != null) {
@@ -174,7 +181,7 @@ class SingleCoreListeningIOReactor extends AbstractSingleCoreIOReactor implement
 
                 final SelectionKey key = serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);
                 key.attach(request);
-                final ListenerEndpoint endpoint = new ListenerEndpointImpl(key, socket.getLocalSocketAddress());
+                final ListenerEndpointImpl endpoint = new ListenerEndpointImpl(key, request.attachment, socket.getLocalSocketAddress());
                 this.endpoints.put(endpoint, Boolean.TRUE);
                 request.completed(endpoint);
             } catch (final IOException ex) {
@@ -187,7 +194,7 @@ class SingleCoreListeningIOReactor extends AbstractSingleCoreIOReactor implement
     @Override
     public Set<ListenerEndpoint> getEndpoints() {
         final Set<ListenerEndpoint> set = new HashSet<>();
-        final Iterator<ListenerEndpoint> it = this.endpoints.keySet().iterator();
+        final Iterator<ListenerEndpointImpl> it = this.endpoints.keySet().iterator();
         while (it.hasNext()) {
             final ListenerEndpoint endpoint = it.next();
             if (!endpoint.isClosed()) {
@@ -202,12 +209,12 @@ class SingleCoreListeningIOReactor extends AbstractSingleCoreIOReactor implement
     @Override
     public void pause() throws IOException {
         if (paused.compareAndSet(false, true)) {
-            final Iterator<ListenerEndpoint> it = this.endpoints.keySet().iterator();
+            final Iterator<ListenerEndpointImpl> it = this.endpoints.keySet().iterator();
             while (it.hasNext()) {
-                final ListenerEndpoint endpoint = it.next();
+                final ListenerEndpointImpl endpoint = it.next();
                 if (!endpoint.isClosed()) {
                     endpoint.close();
-                    this.requestQueue.add(new ListenerEndpointRequest(endpoint.getAddress(), null));
+                    this.requestQueue.add(new ListenerEndpointRequest(endpoint.address, endpoint.attachment, null));
                 }
                 it.remove();
             }