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 2022/02/27 22:25:12 UTC

[httpcomponents-core] branch master updated (50c8740 -> 3ca313f)

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.


 discard 50c8740  HTTPCLIENT-2201: protocol exception thrown while consuming pushed headers can leave the pushed stream on the client side in an inconsistent state
 discard e4cc649  Test coverage
 discard 8561cbd  Protocol negotiators now keep track the negotiated HTTP protocol version and can report it to the application layer; improved HTTP protocol negotiation
     new dd90c23  Protocol negotiators now keep track the negotiated HTTP protocol version and can report it to the application layer; improved HTTP protocol negotiation
     new 6abb74e  Test coverage
     new 3ca313f  HTTPCLIENT-2201: protocol exception thrown while consuming pushed headers can leave the pushed stream on the client side in an inconsistent state

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (50c8740)
            \
             N -- N -- N   refs/heads/master (3ca313f)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 3 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:
 .../java/org/apache/hc/core5/http2/impl/nio/HttpProtocolNegotiator.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

[httpcomponents-core] 01/03: Protocol negotiators now keep track the negotiated HTTP protocol version and can report it to the application layer; improved HTTP protocol negotiation

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 dd90c234e70ccff077e95c14c944b76fa7be8f32
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Thu Jan 27 18:37:51 2022 +0100

    Protocol negotiators now keep track the negotiated HTTP protocol version and can report it to the application layer; improved HTTP protocol negotiation
---
 ...Negotiator.java => ClientH2PrefaceHandler.java} |  19 ++-
 .../http2/impl/nio/ClientH2UpgradeHandler.java     |   2 +-
 ...Handler.java => ClientHttp1UpgradeHandler.java} |  25 +--
 ...a => ClientHttpProtocolNegotiationStarter.java} |  32 ++--
 .../impl/nio/ClientHttpProtocolNegotiator.java     | 187 ---------------------
 ...tiatorBase.java => HttpProtocolNegotiator.java} |  82 ++++++---
 ...NegotiatorBase.java => PrefaceHandlerBase.java} |   7 +-
 ...Negotiator.java => ServerH2PrefaceHandler.java} |  10 +-
 .../http2/impl/nio/ServerH2UpgradeHandler.java     |   2 +-
 ...Handler.java => ServerHttp1UpgradeHandler.java} |  31 ++--
 ...a => ServerHttpProtocolNegotiationStarter.java} |  46 +++--
 .../impl/nio/ServerHttpProtocolNegotiator.java     | 182 --------------------
 .../H2MultiplexingRequesterBootstrap.java          |   4 +-
 .../impl/nio/bootstrap/H2RequesterBootstrap.java   |   4 +-
 .../impl/nio/bootstrap/H2ServerBootstrap.java      |   4 +-
 .../apache/hc/core5/testing/nio/H2TestClient.java  |   4 +-
 .../apache/hc/core5/testing/nio/H2TestServer.java  |   4 +-
 ... InternalClientProtocolNegotiationStarter.java} |  27 ++-
 ... InternalServerProtocolNegotiationStarter.java} |  30 +++-
 .../http/impl/nio/ClientHttp1IOEventHandler.java   |   8 +
 .../http/impl/nio/ServerHttp1IOEventHandler.java   |   8 +
 .../hc/core5/reactor/InternalDataChannel.java      |   6 +-
 22 files changed, 235 insertions(+), 489 deletions(-)

diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2OnlyClientProtocolNegotiator.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2PrefaceHandler.java
similarity index 90%
rename from httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2OnlyClientProtocolNegotiator.java
rename to httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2PrefaceHandler.java
index 388e034..9730b3a 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2OnlyClientProtocolNegotiator.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2PrefaceHandler.java
@@ -47,10 +47,16 @@ import org.apache.hc.core5.util.TextUtils;
  * client side of the HTTP/2 protocol negotiation handshake always forcing the choice
  * of HTTP/2.
  *
- * @since 5.0
+ * @since 5.2
  */
 @Internal
-public class H2OnlyClientProtocolNegotiator extends ProtocolNegotiatorBase {
+public class ClientH2PrefaceHandler extends PrefaceHandlerBase {
+
+    // PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
+    final static byte[] PREFACE = new byte[] {
+            0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50,
+            0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d,
+            0x0d, 0x0a, 0x0d, 0x0a};
 
     private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory;
     private final boolean strictALPNHandshake;
@@ -59,7 +65,7 @@ public class H2OnlyClientProtocolNegotiator extends ProtocolNegotiatorBase {
     private volatile ByteBuffer preface;
     private volatile BufferedData inBuf;
 
-    public H2OnlyClientProtocolNegotiator(
+    public ClientH2PrefaceHandler(
             final ProtocolIOSession ioSession,
             final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
             final boolean strictALPNHandshake) {
@@ -69,7 +75,7 @@ public class H2OnlyClientProtocolNegotiator extends ProtocolNegotiatorBase {
     /**
      * @since 5.1
      */
-    public H2OnlyClientProtocolNegotiator(
+    public ClientH2PrefaceHandler(
             final ProtocolIOSession ioSession,
             final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
             final boolean strictALPNHandshake,
@@ -94,7 +100,7 @@ public class H2OnlyClientProtocolNegotiator extends ProtocolNegotiatorBase {
                 }
             }
         }
-        this.preface = ByteBuffer.wrap(ClientHttpProtocolNegotiator.PREFACE);
+        this.preface = ByteBuffer.wrap(PREFACE);
         ioSession.setEvent(SelectionKey.OP_WRITE);
     }
 
@@ -104,9 +110,8 @@ public class H2OnlyClientProtocolNegotiator extends ProtocolNegotiatorBase {
         }
         if (!preface.hasRemaining()) {
             session.clearEvent(SelectionKey.OP_WRITE);
-            final ClientH2StreamMultiplexer streamMultiplexer = http2StreamHandlerFactory.create(ioSession);
             final ByteBuffer data = inBuf != null ? inBuf.data() : null;
-            startProtocol(new ClientH2IOEventHandler(streamMultiplexer), data);
+            startProtocol(new ClientH2IOEventHandler(http2StreamHandlerFactory.create(ioSession)), data);
             if (inBuf != null) {
                 inBuf.clear();
             }
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2UpgradeHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2UpgradeHandler.java
index ee548f8..9009a9f 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2UpgradeHandler.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2UpgradeHandler.java
@@ -56,7 +56,7 @@ public class ClientH2UpgradeHandler implements ProtocolUpgradeHandler {
 
     @Override
     public void upgrade(final ProtocolIOSession ioSession, final FutureCallback<ProtocolIOSession> callback) {
-        final HttpConnectionEventHandler protocolNegotiator = new H2OnlyClientProtocolNegotiator(
+        final HttpConnectionEventHandler protocolNegotiator = new ClientH2PrefaceHandler(
                 ioSession, http2StreamHandlerFactory, true, callback);
         ioSession.upgrade(protocolNegotiator);
         try {
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2UpgradeHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttp1UpgradeHandler.java
similarity index 67%
copy from httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2UpgradeHandler.java
copy to httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttp1UpgradeHandler.java
index ee548f8..082bc68 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2UpgradeHandler.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttp1UpgradeHandler.java
@@ -33,36 +33,39 @@ import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.concurrent.FutureCallback;
-import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler;
+import org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler;
+import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory;
 import org.apache.hc.core5.reactor.ProtocolIOSession;
 import org.apache.hc.core5.reactor.ProtocolUpgradeHandler;
 import org.apache.hc.core5.util.Args;
 
 /**
  * Protocol upgrade handler that upgrades the underlying {@link ProtocolIOSession}
- * to HTTP/2 in case of a successful protocol negotiation.
+ * to HTTP/1.1 in case of a successful protocol negotiation or as a default fall-back.
  *
  * @since 5.2
  */
 @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
 @Internal
-public class ClientH2UpgradeHandler implements ProtocolUpgradeHandler {
+public class ClientHttp1UpgradeHandler implements ProtocolUpgradeHandler {
 
-    private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory;
+    private final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory;
 
-    public ClientH2UpgradeHandler(final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory) {
-        this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory");
+    public ClientHttp1UpgradeHandler(final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory) {
+        this.http1StreamHandlerFactory = Args.notNull(http1StreamHandlerFactory, "HTTP/1.1 stream handler factory");
     }
 
     @Override
     public void upgrade(final ProtocolIOSession ioSession, final FutureCallback<ProtocolIOSession> callback) {
-        final HttpConnectionEventHandler protocolNegotiator = new H2OnlyClientProtocolNegotiator(
-                ioSession, http2StreamHandlerFactory, true, callback);
-        ioSession.upgrade(protocolNegotiator);
+        final ClientHttp1IOEventHandler eventHandler = new ClientHttp1IOEventHandler(http1StreamHandlerFactory.create(ioSession));
+        ioSession.upgrade(eventHandler);
         try {
-            protocolNegotiator.connected(ioSession);
+            eventHandler.connected(ioSession);
+            if (callback != null) {
+                callback.completed(ioSession);
+            }
         } catch (final IOException ex) {
-            protocolNegotiator.exception(ioSession, ex);
+            eventHandler.exception(ioSession, ex);
         }
     }
 
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/ClientHttpProtocolNegotiationStarter.java
similarity index 73%
rename from httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiatorFactory.java
rename to httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiationStarter.java
index 82fe49b..a07f38a 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/ClientHttpProtocolNegotiationStarter.java
@@ -31,9 +31,12 @@ 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.ClientHttp1IOEventHandler;
 import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory;
+import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler;
 import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
 import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
 import org.apache.hc.core5.reactor.EndpointParameters;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
 import org.apache.hc.core5.reactor.ProtocolIOSession;
@@ -41,13 +44,15 @@ import org.apache.hc.core5.util.Args;
 import org.apache.hc.core5.util.Timeout;
 
 /**
- * {@link ClientHttpProtocolNegotiator} factory.
+ * Client I/O event starter that prepares I/O sessions for an initial protocol handshake.
+ * This class may return a different {@link org.apache.hc.core5.reactor.IOEventHandler}
+ * implementation based on the current HTTP version policy.
  *
- * @since 5.0
+ * @since 5.1
  */
 @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
 @Internal
-public class ClientHttpProtocolNegotiatorFactory implements IOEventHandlerFactory {
+public class ClientHttpProtocolNegotiationStarter implements IOEventHandlerFactory {
 
     private final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory;
     private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory;
@@ -55,7 +60,7 @@ public class ClientHttpProtocolNegotiatorFactory implements IOEventHandlerFactor
     private final TlsStrategy tlsStrategy;
     private final Timeout handshakeTimeout;
 
-    public ClientHttpProtocolNegotiatorFactory(
+    public ClientHttpProtocolNegotiationStarter(
             final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory,
             final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
             final HttpVersionPolicy versionPolicy,
@@ -69,7 +74,7 @@ public class ClientHttpProtocolNegotiatorFactory implements IOEventHandlerFactor
     }
 
     @Override
-    public ClientHttpProtocolNegotiator createHandler(final ProtocolIOSession ioSession, final Object attachment) {
+    public HttpConnectionEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) {
         HttpVersionPolicy endpointPolicy = versionPolicy;
         if (attachment instanceof EndpointParameters) {
             final EndpointParameters params = (EndpointParameters) attachment;
@@ -80,11 +85,18 @@ public class ClientHttpProtocolNegotiatorFactory implements IOEventHandlerFactor
                 endpointPolicy = (HttpVersionPolicy) params.getAttachment();
             }
         }
-        return new ClientHttpProtocolNegotiator(
-                ioSession,
-                http1StreamHandlerFactory,
-                http2StreamHandlerFactory,
-                endpointPolicy);
+
+        ioSession.registerProtocol(ApplicationProtocol.HTTP_1_1.id, new ClientHttp1UpgradeHandler(http1StreamHandlerFactory));
+        ioSession.registerProtocol(ApplicationProtocol.HTTP_2.id, new ClientH2UpgradeHandler(http2StreamHandlerFactory));
+
+        switch (endpointPolicy) {
+            case FORCE_HTTP_2:
+                return new ClientH2PrefaceHandler(ioSession, http2StreamHandlerFactory, false);
+            case FORCE_HTTP_1:
+                return new ClientHttp1IOEventHandler(http1StreamHandlerFactory.create(ioSession));
+            default:
+                return new HttpProtocolNegotiator(ioSession, null);
+        }
     }
 
 }
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiator.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiator.java
deleted file mode 100644
index 9c2152f..0000000
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiator.java
+++ /dev/null
@@ -1,187 +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.http2.impl.nio;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectionKey;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.hc.core5.annotation.Internal;
-import org.apache.hc.core5.concurrent.FutureCallback;
-import org.apache.hc.core5.http.impl.nio.BufferedData;
-import org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler;
-import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory;
-import org.apache.hc.core5.http2.HttpVersionPolicy;
-import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
-import org.apache.hc.core5.reactor.IOSession;
-import org.apache.hc.core5.reactor.ProtocolIOSession;
-import org.apache.hc.core5.reactor.ssl.TlsDetails;
-import org.apache.hc.core5.util.Args;
-
-/**
- * I/O event handler for events fired by {@link ProtocolIOSession} that implements
- * client side of the HTTP/2 protocol negotiation handshake
- * based on {@link HttpVersionPolicy} configuration.
- *
- * @since 5.0
- */
-@Internal
-public class ClientHttpProtocolNegotiator extends ProtocolNegotiatorBase {
-
-    // PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
-    final static byte[] PREFACE = new byte[] {
-            0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50,
-            0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d,
-            0x0d, 0x0a, 0x0d, 0x0a};
-
-    private final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory;
-    private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory;
-    private final HttpVersionPolicy versionPolicy;
-    private final AtomicBoolean initialized;
-
-    private volatile ByteBuffer preface;
-    private volatile BufferedData inBuf;
-
-    public ClientHttpProtocolNegotiator(
-            final ProtocolIOSession ioSession,
-            final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory,
-            final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
-            final HttpVersionPolicy versionPolicy) {
-        this(ioSession, http1StreamHandlerFactory, http2StreamHandlerFactory, versionPolicy, null);
-    }
-
-    /**
-     * @since 5.1
-     */
-    public ClientHttpProtocolNegotiator(
-            final ProtocolIOSession ioSession,
-            final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory,
-            final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
-            final HttpVersionPolicy versionPolicy,
-            final FutureCallback<ProtocolIOSession> resultCallback) {
-        super(ioSession, resultCallback);
-        this.http1StreamHandlerFactory = Args.notNull(http1StreamHandlerFactory, "HTTP/1.1 stream handler factory");
-        this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory");
-        this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE;
-        this.initialized = new AtomicBoolean();
-    }
-
-    private void startHttp1() throws IOException {
-        final ByteBuffer data = inBuf != null ? inBuf.data() : null;
-        startProtocol(new ClientHttp1IOEventHandler(http1StreamHandlerFactory.create(ioSession)), data);
-        ioSession.registerProtocol(ApplicationProtocol.HTTP_2.id, new ClientH2UpgradeHandler(http2StreamHandlerFactory));
-        if (inBuf != null) {
-            inBuf.clear();
-        }
-    }
-
-    private void startHttp2() throws IOException {
-        final ByteBuffer data = inBuf != null ? inBuf.data() : null;
-        startProtocol(new ClientH2IOEventHandler(http2StreamHandlerFactory.create(ioSession)), data);
-        if (inBuf != null) {
-            inBuf.clear();
-        }
-    }
-
-    private void initialize() throws IOException {
-        switch (versionPolicy) {
-            case NEGOTIATE:
-                final TlsDetails tlsDetails = ioSession.getTlsDetails();
-                if (tlsDetails != null) {
-                    if (ApplicationProtocol.HTTP_2.id.equals(tlsDetails.getApplicationProtocol())) {
-                        // Proceed with the H2 preface
-                        preface = ByteBuffer.wrap(PREFACE);
-                    }
-                }
-                break;
-            case FORCE_HTTP_2:
-                preface = ByteBuffer.wrap(PREFACE);
-                break;
-        }
-        if (preface == null) {
-            startHttp1();
-        } else {
-            ioSession.setEvent(SelectionKey.OP_WRITE);
-        }
-    }
-
-    private void writeOutPreface(final IOSession session) throws IOException {
-        if (preface.hasRemaining()) {
-            session.write(preface);
-        }
-        if (!preface.hasRemaining()) {
-            session.clearEvent(SelectionKey.OP_WRITE);
-            startHttp2();
-            preface = null;
-        }
-    }
-
-    @Override
-    public void connected(final IOSession session) throws IOException {
-        if (initialized.compareAndSet(false, true)) {
-            initialize();
-        }
-        if (preface != null) {
-            writeOutPreface(session);
-        }
-    }
-
-    @Override
-    public void inputReady(final IOSession session, final ByteBuffer src) throws IOException  {
-        if (src != null) {
-            if (inBuf == null) {
-                inBuf = BufferedData.allocate(src.remaining());
-            }
-            inBuf.put(src);
-        }
-        if (preface != null) {
-            writeOutPreface(session);
-        } else {
-            throw new ProtocolNegotiationException("Unexpected input");
-        }
-    }
-
-    @Override
-    public void outputReady(final IOSession session) throws IOException {
-        if (initialized.compareAndSet(false, true)) {
-            initialize();
-        }
-        if (preface != null) {
-            writeOutPreface(session);
-        } else {
-            throw new ProtocolNegotiationException("Unexpected output");
-        }
-    }
-
-    @Override
-    public String toString() {
-        return getClass().getName() + "/" + versionPolicy;
-    }
-
-}
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ProtocolNegotiatorBase.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/HttpProtocolNegotiator.java
similarity index 66%
copy from httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ProtocolNegotiatorBase.java
copy to httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/HttpProtocolNegotiator.java
index 89e787c..14f0bb4 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ProtocolNegotiatorBase.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/HttpProtocolNegotiator.java
@@ -35,46 +35,79 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import javax.net.ssl.SSLSession;
 
+import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.concurrent.FutureCallback;
 import org.apache.hc.core5.http.ConnectionClosedException;
 import org.apache.hc.core5.http.EndpointDetails;
+import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler;
 import org.apache.hc.core5.http.nio.command.CommandSupport;
+import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
 import org.apache.hc.core5.io.CloseMode;
 import org.apache.hc.core5.io.SocketTimeoutExceptionFactory;
 import org.apache.hc.core5.reactor.IOSession;
 import org.apache.hc.core5.reactor.ProtocolIOSession;
 import org.apache.hc.core5.reactor.ssl.TlsDetails;
 import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.TextUtils;
 import org.apache.hc.core5.util.Timeout;
 
-abstract class ProtocolNegotiatorBase implements HttpConnectionEventHandler {
+/**
+ * @since 5.2
+ */
+@Internal
+public class HttpProtocolNegotiator implements HttpConnectionEventHandler {
 
-    final ProtocolIOSession ioSession;
-    private final AtomicReference<HttpConnectionEventHandler> protocolHandlerRef;
+    private final ProtocolIOSession ioSession;
     private final FutureCallback<ProtocolIOSession> resultCallback;
     private final AtomicBoolean completed;
+    private final AtomicReference<ProtocolVersion> negotiatedProtocolRef;
 
-    ProtocolNegotiatorBase(
+    public HttpProtocolNegotiator(
             final ProtocolIOSession ioSession,
             final FutureCallback<ProtocolIOSession> resultCallback) {
         this.ioSession = Args.notNull(ioSession, "I/O session");
-        this.protocolHandlerRef = new AtomicReference<>();
         this.resultCallback = resultCallback;
         this.completed = new AtomicBoolean();
+        this.negotiatedProtocolRef = new AtomicReference<>();
     }
 
-    void startProtocol(final HttpConnectionEventHandler protocolHandler, final ByteBuffer data) throws IOException {
-        protocolHandlerRef.set(protocolHandler);
-        ioSession.upgrade(protocolHandler);
-        protocolHandler.connected(ioSession);
-        if (data != null && data.hasRemaining()) {
-            protocolHandler.inputReady(ioSession, data);
-        }
-        if (completed.compareAndSet(false, true) && resultCallback != null) {
-            resultCallback.completed(ioSession);
+    void startProtocol(final HttpVersion httpVersion) {
+        ioSession.switchProtocol(
+                httpVersion == HttpVersion.HTTP_2 ? ApplicationProtocol.HTTP_2.id : ApplicationProtocol.HTTP_1_1.id,
+                resultCallback);
+        negotiatedProtocolRef.set(httpVersion);
+    }
+
+    @Override
+    public void connected(final IOSession session) throws IOException {
+        final HttpVersion httpVersion;
+        final TlsDetails tlsDetails = ioSession.getTlsDetails();
+        if (tlsDetails != null) {
+            final String appProtocol = tlsDetails.getApplicationProtocol();
+            if (TextUtils.isEmpty(appProtocol)) {
+                httpVersion = HttpVersion.HTTP_1_1;
+            } else if (appProtocol.equals(ApplicationProtocol.HTTP_1_1.id)) {
+                httpVersion = HttpVersion.HTTP_1_1;
+            } else if (appProtocol.equals(ApplicationProtocol.HTTP_2.id)) {
+                httpVersion = HttpVersion.HTTP_2;
+            } else {
+                throw new ProtocolNegotiationException("Unsupported application protocol: " + appProtocol);
+            }
+        } else {
+            httpVersion = HttpVersion.HTTP_1_1;
         }
+        startProtocol(httpVersion);
+    }
+    @Override
+    public void inputReady(final IOSession session, final ByteBuffer src) throws IOException  {
+        throw new ProtocolNegotiationException("Unexpected input");
+    }
+
+    @Override
+    public void outputReady(final IOSession session) throws IOException {
+        throw new ProtocolNegotiationException("Unexpected output");
     }
 
     @Override
@@ -84,14 +117,9 @@ abstract class ProtocolNegotiatorBase implements HttpConnectionEventHandler {
 
     @Override
     public void exception(final IOSession session, final Exception cause) {
-        final HttpConnectionEventHandler protocolHandler = protocolHandlerRef.get();
         try {
             session.close(CloseMode.IMMEDIATE);
-            if (protocolHandler != null) {
-                protocolHandler.exception(session, cause);
-            } else {
-                CommandSupport.failCommands(session, cause);
-            }
+            CommandSupport.failCommands(session, cause);
         } catch (final Exception ex) {
             if (completed.compareAndSet(false, true) && resultCallback != null) {
                 resultCallback.failed(ex);
@@ -101,13 +129,8 @@ abstract class ProtocolNegotiatorBase implements HttpConnectionEventHandler {
 
     @Override
     public void disconnected(final IOSession session) {
-        final HttpConnectionEventHandler protocolHandler = protocolHandlerRef.getAndSet(null);
         try {
-            if (protocolHandler != null) {
-                protocolHandler.disconnected(ioSession);
-            } else {
-                CommandSupport.cancelCommands(session);
-            }
+            CommandSupport.cancelCommands(session);
         } finally {
             if (completed.compareAndSet(false, true) && resultCallback != null) {
                 resultCallback.failed(new ConnectionClosedException());
@@ -138,7 +161,7 @@ abstract class ProtocolNegotiatorBase implements HttpConnectionEventHandler {
 
     @Override
     public ProtocolVersion getProtocolVersion() {
-        return null;
+        return negotiatedProtocolRef.get();
     }
 
     @Override
@@ -166,4 +189,9 @@ abstract class ProtocolNegotiatorBase implements HttpConnectionEventHandler {
         ioSession.close(closeMode);
     }
 
+    @Override
+    public String toString() {
+        return getClass().getName();
+    }
+
 }
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ProtocolNegotiatorBase.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/PrefaceHandlerBase.java
similarity index 96%
rename from httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ProtocolNegotiatorBase.java
rename to httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/PrefaceHandlerBase.java
index 89e787c..fbe96d1 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ProtocolNegotiatorBase.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/PrefaceHandlerBase.java
@@ -38,6 +38,7 @@ import javax.net.ssl.SSLSession;
 import org.apache.hc.core5.concurrent.FutureCallback;
 import org.apache.hc.core5.http.ConnectionClosedException;
 import org.apache.hc.core5.http.EndpointDetails;
+import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler;
 import org.apache.hc.core5.http.nio.command.CommandSupport;
@@ -49,14 +50,14 @@ import org.apache.hc.core5.reactor.ssl.TlsDetails;
 import org.apache.hc.core5.util.Args;
 import org.apache.hc.core5.util.Timeout;
 
-abstract class ProtocolNegotiatorBase implements HttpConnectionEventHandler {
+abstract class PrefaceHandlerBase implements HttpConnectionEventHandler {
 
     final ProtocolIOSession ioSession;
     private final AtomicReference<HttpConnectionEventHandler> protocolHandlerRef;
     private final FutureCallback<ProtocolIOSession> resultCallback;
     private final AtomicBoolean completed;
 
-    ProtocolNegotiatorBase(
+    PrefaceHandlerBase(
             final ProtocolIOSession ioSession,
             final FutureCallback<ProtocolIOSession> resultCallback) {
         this.ioSession = Args.notNull(ioSession, "I/O session");
@@ -138,7 +139,7 @@ abstract class ProtocolNegotiatorBase implements HttpConnectionEventHandler {
 
     @Override
     public ProtocolVersion getProtocolVersion() {
-        return null;
+        return HttpVersion.HTTP_2;
     }
 
     @Override
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2OnlyServerHttpProtocolNegotiator.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2PrefaceHandler.java
similarity index 93%
rename from httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2OnlyServerHttpProtocolNegotiator.java
rename to httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2PrefaceHandler.java
index a770f01..35c80c2 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2OnlyServerHttpProtocolNegotiator.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2PrefaceHandler.java
@@ -42,23 +42,23 @@ import org.apache.hc.core5.util.Args;
  * I/O event handler for events fired by {@link ProtocolIOSession} that implements
  * server side of the HTTP/2 protocol negotiation handshake.
  *
- * @since 5.1
+ * @since 5.2
  */
 @Internal
-public class H2OnlyServerHttpProtocolNegotiator extends ProtocolNegotiatorBase {
+public class ServerH2PrefaceHandler extends PrefaceHandlerBase {
 
-    final static byte[] PREFACE = ClientHttpProtocolNegotiator.PREFACE;
+    final static byte[] PREFACE = ClientH2PrefaceHandler.PREFACE;
 
     private final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory;
     private final BufferedData inBuf;
 
-    public H2OnlyServerHttpProtocolNegotiator(
+    public ServerH2PrefaceHandler(
             final ProtocolIOSession ioSession,
             final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory) {
         this(ioSession, http2StreamHandlerFactory, null);
     }
 
-    public H2OnlyServerHttpProtocolNegotiator(
+    public ServerH2PrefaceHandler(
             final ProtocolIOSession ioSession,
             final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory,
             final FutureCallback<ProtocolIOSession> resultCallback) {
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2UpgradeHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2UpgradeHandler.java
index 181a465..02b23f5 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2UpgradeHandler.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2UpgradeHandler.java
@@ -56,7 +56,7 @@ public class ServerH2UpgradeHandler implements ProtocolUpgradeHandler {
 
     @Override
     public void upgrade(final ProtocolIOSession ioSession, final FutureCallback<ProtocolIOSession> callback) {
-        final HttpConnectionEventHandler protocolNegotiator = new H2OnlyServerHttpProtocolNegotiator(
+        final HttpConnectionEventHandler protocolNegotiator = new ServerH2PrefaceHandler(
                 ioSession, http2StreamHandlerFactory, callback);
         ioSession.upgrade(protocolNegotiator);
         try {
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2UpgradeHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttp1UpgradeHandler.java
similarity index 61%
copy from httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2UpgradeHandler.java
copy to httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttp1UpgradeHandler.java
index 181a465..3093612 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2UpgradeHandler.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttp1UpgradeHandler.java
@@ -33,36 +33,45 @@ import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.concurrent.FutureCallback;
-import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.impl.nio.ServerHttp1IOEventHandler;
+import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory;
 import org.apache.hc.core5.reactor.ProtocolIOSession;
 import org.apache.hc.core5.reactor.ProtocolUpgradeHandler;
+import org.apache.hc.core5.reactor.ssl.TlsDetails;
 import org.apache.hc.core5.util.Args;
 
 /**
  * Protocol upgrade handler that upgrades the underlying {@link ProtocolIOSession}
- * to HTTP/2 in case of a successful protocol negotiation.
+ * to HTTP/1.1 in case of a successful protocol negotiation or as a default fall-back.
  *
  * @since 5.2
  */
 @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
 @Internal
-public class ServerH2UpgradeHandler implements ProtocolUpgradeHandler {
+public class ServerHttp1UpgradeHandler implements ProtocolUpgradeHandler {
 
-    private final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory;
+    private final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory;
 
-    public ServerH2UpgradeHandler(final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory) {
-        this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory");
+    public ServerHttp1UpgradeHandler(final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory) {
+        this.http1StreamHandlerFactory = Args.notNull(http1StreamHandlerFactory, "HTTP/1.1 stream handler factory");
     }
 
     @Override
     public void upgrade(final ProtocolIOSession ioSession, final FutureCallback<ProtocolIOSession> callback) {
-        final HttpConnectionEventHandler protocolNegotiator = new H2OnlyServerHttpProtocolNegotiator(
-                ioSession, http2StreamHandlerFactory, callback);
-        ioSession.upgrade(protocolNegotiator);
+        final TlsDetails tlsDetails = ioSession.getTlsDetails();
+        final ServerHttp1IOEventHandler eventHandler = new ServerHttp1IOEventHandler(http1StreamHandlerFactory.create(
+                tlsDetails != null ? URIScheme.HTTPS.id : URIScheme.HTTP.id,
+                ioSession));
+        ioSession.upgrade(eventHandler);
+        ioSession.upgrade(eventHandler);
         try {
-            protocolNegotiator.connected(ioSession);
+            eventHandler.connected(ioSession);
+            if (callback != null) {
+                callback.completed(ioSession);
+            }
         } catch (final IOException ex) {
-            protocolNegotiator.exception(ioSession, ex);
+            eventHandler.exception(ioSession, ex);
         }
     }
 
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/ServerHttpProtocolNegotiationStarter.java
similarity index 60%
rename from httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttpProtocolNegotiatorFactory.java
rename to httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttpProtocolNegotiationStarter.java
index 9143538..e9f1eac 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/ServerHttpProtocolNegotiationStarter.java
@@ -31,9 +31,12 @@ 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.HttpConnectionEventHandler;
+import org.apache.hc.core5.http.impl.nio.ServerHttp1IOEventHandler;
 import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory;
 import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
 import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
 import org.apache.hc.core5.reactor.EndpointParameters;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
 import org.apache.hc.core5.reactor.ProtocolIOSession;
@@ -41,50 +44,61 @@ import org.apache.hc.core5.util.Args;
 import org.apache.hc.core5.util.Timeout;
 
 /**
- * {@link ServerHttpProtocolNegotiator} factory.
+ * Server I/O event starter that prepares I/O sessions for an initial protocol handshake.
+ * This class may return a different {@link org.apache.hc.core5.reactor.IOEventHandler}
+ * implementation based on the current HTTP version policy.
  *
- * @since 5.0
+ * @since 5.1
  */
 @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
 @Internal
-public class ServerHttpProtocolNegotiatorFactory implements IOEventHandlerFactory {
+public class ServerHttpProtocolNegotiationStarter implements IOEventHandlerFactory {
 
-    private final ServerHttp1StreamDuplexerFactory http1StreamDuplexerFactory;
-    private final ServerH2StreamMultiplexerFactory http2StreamMultiplexerFactory;
+    private final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory;
+    private final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory;
     private final HttpVersionPolicy versionPolicy;
     private final TlsStrategy tlsStrategy;
     private final Timeout handshakeTimeout;
 
-    public ServerHttpProtocolNegotiatorFactory(
-            final ServerHttp1StreamDuplexerFactory http1StreamDuplexerFactory,
-            final ServerH2StreamMultiplexerFactory http2StreamMultiplexerFactory,
+    public ServerHttpProtocolNegotiationStarter(
+            final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory,
+            final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory,
             final HttpVersionPolicy versionPolicy,
             final TlsStrategy tlsStrategy,
             final Timeout handshakeTimeout) {
-        this.http1StreamDuplexerFactory = Args.notNull(http1StreamDuplexerFactory, "HTTP/1.1 stream handler factory");
-        this.http2StreamMultiplexerFactory = Args.notNull(http2StreamMultiplexerFactory, "HTTP/2 stream handler factory");
+        this.http1StreamHandlerFactory = Args.notNull(http1StreamHandlerFactory, "HTTP/1.1 stream handler factory");
+        this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory");
         this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE;
         this.tlsStrategy = tlsStrategy;
         this.handshakeTimeout = handshakeTimeout;
     }
 
     @Override
-    public ServerHttpProtocolNegotiator createHandler(final ProtocolIOSession ioSession, final Object attachment) {
+    public HttpConnectionEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) {
         HttpVersionPolicy endpointPolicy = versionPolicy;
+        URIScheme uriScheme = URIScheme.HTTP;
         if (attachment instanceof EndpointParameters) {
             final EndpointParameters params = (EndpointParameters) attachment;
             if (tlsStrategy != null && URIScheme.HTTPS.same(params.getScheme())) {
+                uriScheme = URIScheme.HTTPS;
                 tlsStrategy.upgrade(ioSession, params, params.getAttachment(), handshakeTimeout, null);
             }
             if (params.getAttachment() instanceof HttpVersionPolicy) {
                 endpointPolicy = (HttpVersionPolicy) params.getAttachment();
             }
         }
-        return new ServerHttpProtocolNegotiator(
-                ioSession,
-                http1StreamDuplexerFactory,
-                http2StreamMultiplexerFactory,
-                endpointPolicy);
+
+        ioSession.registerProtocol(ApplicationProtocol.HTTP_1_1.id, new ServerHttp1UpgradeHandler(http1StreamHandlerFactory));
+        ioSession.registerProtocol(ApplicationProtocol.HTTP_2.id, new ServerH2UpgradeHandler(http2StreamHandlerFactory));
+
+        switch (endpointPolicy) {
+            case FORCE_HTTP_2:
+                return new ServerH2PrefaceHandler(ioSession, http2StreamHandlerFactory);
+            case FORCE_HTTP_1:
+                return new ServerHttp1IOEventHandler(http1StreamHandlerFactory.create(uriScheme.id, ioSession));
+            default:
+                return new HttpProtocolNegotiator(ioSession, null);
+        }
     }
 
 }
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttpProtocolNegotiator.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttpProtocolNegotiator.java
deleted file mode 100644
index f6febff..0000000
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttpProtocolNegotiator.java
+++ /dev/null
@@ -1,182 +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.http2.impl.nio;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.hc.core5.annotation.Internal;
-import org.apache.hc.core5.concurrent.FutureCallback;
-import org.apache.hc.core5.http.ConnectionClosedException;
-import org.apache.hc.core5.http.URIScheme;
-import org.apache.hc.core5.http.impl.nio.BufferedData;
-import org.apache.hc.core5.http.impl.nio.ServerHttp1IOEventHandler;
-import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexer;
-import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory;
-import org.apache.hc.core5.http2.HttpVersionPolicy;
-import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
-import org.apache.hc.core5.reactor.IOSession;
-import org.apache.hc.core5.reactor.ProtocolIOSession;
-import org.apache.hc.core5.reactor.ssl.TlsDetails;
-import org.apache.hc.core5.util.Args;
-
-/**
- * I/O event handler for events fired by {@link ProtocolIOSession} that implements
- * server side of the HTTP/2 protocol negotiation handshake
- * based on {@link HttpVersionPolicy} configuration.
- *
- * @since 5.0
- */
-@Internal
-public class ServerHttpProtocolNegotiator extends ProtocolNegotiatorBase {
-
-    final static byte[] PREFACE = ClientHttpProtocolNegotiator.PREFACE;
-
-    private final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory;
-    private final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory;
-    private final HttpVersionPolicy versionPolicy;
-    private final BufferedData inBuf;
-    private final AtomicBoolean initialized;
-
-    private volatile boolean expectValidH2Preface;
-
-    public ServerHttpProtocolNegotiator(
-            final ProtocolIOSession ioSession,
-            final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory,
-            final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory,
-            final HttpVersionPolicy versionPolicy) {
-        this(ioSession, http1StreamHandlerFactory, http2StreamHandlerFactory, versionPolicy, null);
-    }
-
-    /**
-     * @since 5.1
-     */
-    public ServerHttpProtocolNegotiator(
-            final ProtocolIOSession ioSession,
-            final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory,
-            final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory,
-            final HttpVersionPolicy versionPolicy,
-            final FutureCallback<ProtocolIOSession> resultCallback) {
-        super(ioSession, resultCallback);
-        this.http1StreamHandlerFactory = Args.notNull(http1StreamHandlerFactory, "HTTP/1.1 stream handler factory");
-        this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory");
-        this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE;
-        this.inBuf = BufferedData.allocate(1024);
-        this.initialized = new AtomicBoolean();
-    }
-
-    private void startHttp1(final TlsDetails tlsDetails, final ByteBuffer data) throws IOException {
-        final ServerHttp1StreamDuplexer http1StreamHandler = http1StreamHandlerFactory.create(
-                tlsDetails != null ? URIScheme.HTTPS.id : URIScheme.HTTP.id,
-                ioSession);
-        startProtocol(new ServerHttp1IOEventHandler(http1StreamHandler), data);
-        ioSession.registerProtocol(ApplicationProtocol.HTTP_2.id, new ServerH2UpgradeHandler(http2StreamHandlerFactory));
-    }
-
-    private void startHttp2(final ByteBuffer data) throws IOException {
-        startProtocol(new ServerH2IOEventHandler(http2StreamHandlerFactory.create(ioSession)), data);
-    }
-
-    private void initialize() throws IOException {
-        final TlsDetails tlsDetails = ioSession.getTlsDetails();
-        switch (versionPolicy) {
-            case NEGOTIATE:
-                if (tlsDetails != null &&
-                        ApplicationProtocol.HTTP_2.id.equals(tlsDetails.getApplicationProtocol())) {
-                    expectValidH2Preface = true;
-                }
-                break;
-            case FORCE_HTTP_2:
-                if (tlsDetails == null ||
-                        !ApplicationProtocol.HTTP_1_1.id.equals(tlsDetails.getApplicationProtocol())) {
-                    expectValidH2Preface = true;
-                }
-                break;
-            case FORCE_HTTP_1:
-                startHttp1(tlsDetails, null);
-                break;
-        }
-    }
-
-    @Override
-    public void connected(final IOSession session) throws IOException {
-        if (initialized.compareAndSet(false, true)) {
-            initialize();
-        }
-    }
-
-    @Override
-    public void inputReady(final IOSession session, final ByteBuffer src) throws IOException {
-        if (src != null) {
-            inBuf.put(src);
-        }
-        boolean endOfStream = false;
-        if (inBuf.length() < PREFACE.length) {
-            final int bytesRead = inBuf.readFrom(session);
-            if (bytesRead == -1) {
-                endOfStream = true;
-            }
-        }
-        final ByteBuffer data = inBuf.data();
-        if (data.remaining() >= PREFACE.length) {
-            boolean validH2Preface = true;
-            for (int i = 0; i < PREFACE.length; i++) {
-                if (data.get() != PREFACE[i]) {
-                    if (expectValidH2Preface) {
-                        throw new ProtocolNegotiationException("Unexpected HTTP/2 preface");
-                    }
-                    validH2Preface = false;
-                }
-            }
-            if (validH2Preface) {
-                startHttp2(data.hasRemaining() ? data : null);
-            } else {
-                data.rewind();
-                startHttp1(ioSession.getTlsDetails(), data);
-            }
-        } else {
-            if (endOfStream) {
-                throw new ConnectionClosedException();
-            }
-        }
-    }
-
-    @Override
-    public void outputReady(final IOSession session) throws IOException {
-        if (initialized.compareAndSet(false, true)) {
-            initialize();
-        }
-    }
-
-    @Override
-    public String toString() {
-        return getClass().getName() + "/" + versionPolicy;
-    }
-
-}
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequesterBootstrap.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequesterBootstrap.java
index 4351b5c..57cd21d 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequesterBootstrap.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequesterBootstrap.java
@@ -42,7 +42,7 @@ import org.apache.hc.core5.http.protocol.UriPatternType;
 import org.apache.hc.core5.http2.config.H2Config;
 import org.apache.hc.core5.http2.impl.H2Processors;
 import org.apache.hc.core5.http2.impl.nio.ClientH2StreamMultiplexerFactory;
-import org.apache.hc.core5.http2.impl.nio.H2OnlyClientProtocolNegotiator;
+import org.apache.hc.core5.http2.impl.nio.ClientH2PrefaceHandler;
 import org.apache.hc.core5.http2.impl.nio.H2StreamListener;
 import org.apache.hc.core5.http2.nio.support.DefaultAsyncPushConsumerFactory;
 import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
@@ -207,7 +207,7 @@ public class H2MultiplexingRequesterBootstrap {
                 streamListener);
         return new H2MultiplexingRequester(
                 ioReactorConfig,
-                (ioSession, attachment) -> new H2OnlyClientProtocolNegotiator(ioSession, http2StreamHandlerFactory, strictALPNHandshake),
+                (ioSession, attachment) -> new ClientH2PrefaceHandler(ioSession, http2StreamHandlerFactory, strictALPNHandshake),
                 ioSessionDecorator,
                 exceptionCallback,
                 sessionListener,
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java
index dc5883c..8ea1490 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java
@@ -52,7 +52,7 @@ import org.apache.hc.core5.http2.HttpVersionPolicy;
 import org.apache.hc.core5.http2.config.H2Config;
 import org.apache.hc.core5.http2.impl.H2Processors;
 import org.apache.hc.core5.http2.impl.nio.ClientH2StreamMultiplexerFactory;
-import org.apache.hc.core5.http2.impl.nio.ClientHttpProtocolNegotiatorFactory;
+import org.apache.hc.core5.http2.impl.nio.ClientHttpProtocolNegotiationStarter;
 import org.apache.hc.core5.http2.impl.nio.H2StreamListener;
 import org.apache.hc.core5.http2.nio.support.DefaultAsyncPushConsumerFactory;
 import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
@@ -334,7 +334,7 @@ public class H2RequesterBootstrap {
                 DefaultContentLengthStrategy.INSTANCE,
                 http1StreamListener);
 
-        final IOEventHandlerFactory ioEventHandlerFactory = new ClientHttpProtocolNegotiatorFactory(
+        final IOEventHandlerFactory ioEventHandlerFactory = new ClientHttpProtocolNegotiationStarter(
                 http1StreamHandlerFactory,
                 http2StreamHandlerFactory,
                 versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE,
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 9201feb..d60f842 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
@@ -65,7 +65,7 @@ import org.apache.hc.core5.http2.config.H2Config;
 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.impl.nio.ServerHttpProtocolNegotiationStarter;
 import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy;
 import org.apache.hc.core5.net.InetAddressUtils;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
@@ -415,7 +415,7 @@ public class H2ServerBootstrap {
                 DefaultContentLengthStrategy.INSTANCE,
                 http1StreamListener);
 
-        final IOEventHandlerFactory ioEventHandlerFactory = new ServerHttpProtocolNegotiatorFactory(
+        final IOEventHandlerFactory ioEventHandlerFactory = new ServerHttpProtocolNegotiationStarter(
                 http1StreamHandlerFactory,
                 http2StreamHandlerFactory,
                 versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE,
diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestClient.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestClient.java
index 48b3682..7197ef6 100644
--- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestClient.java
+++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestClient.java
@@ -88,7 +88,7 @@ public class H2TestClient extends AsyncRequester {
     }
 
     public void start(final HttpProcessor httpProcessor, final H2Config h2Config) throws IOException {
-        start(new InternalClientH2EventHandlerFactory(
+        start(new InternalClientProtocolNegotiationStarter(
                 httpProcessor,
                 new DefaultAsyncPushConsumerFactory(registry),
                 HttpVersionPolicy.FORCE_HTTP_2,
@@ -101,7 +101,7 @@ public class H2TestClient extends AsyncRequester {
     }
 
     public void start(final HttpProcessor httpProcessor, final Http1Config http1Config) throws IOException {
-        start(new InternalClientH2EventHandlerFactory(
+        start(new InternalClientProtocolNegotiationStarter(
                 httpProcessor,
                 new DefaultAsyncPushConsumerFactory(registry),
                 HttpVersionPolicy.FORCE_HTTP_1,
diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestServer.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestServer.java
index 5406f55..149af46 100644
--- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestServer.java
+++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestServer.java
@@ -95,7 +95,7 @@ public class H2TestServer extends AsyncServer {
             final HttpProcessor httpProcessor,
             final Decorator<AsyncServerExchangeHandler> exchangeHandlerDecorator,
             final H2Config h2Config) throws Exception {
-        start(new InternalServerH2EventHandlerFactory(
+        start(new InternalServerProtocolNegotiationStarter(
                 httpProcessor != null ? httpProcessor : H2Processors.server(),
                 new DefaultAsyncResponseExchangeHandlerFactory(
                         registry,
@@ -116,7 +116,7 @@ public class H2TestServer extends AsyncServer {
             final HttpProcessor httpProcessor,
             final Decorator<AsyncServerExchangeHandler> exchangeHandlerDecorator,
             final Http1Config http1Config) throws Exception {
-        start(new InternalServerH2EventHandlerFactory(
+        start(new InternalServerProtocolNegotiationStarter(
                 httpProcessor != null ? httpProcessor : HttpProcessors.server(),
                 new DefaultAsyncResponseExchangeHandlerFactory(
                         registry,
diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalClientH2EventHandlerFactory.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalClientProtocolNegotiationStarter.java
similarity index 80%
rename from httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalClientH2EventHandlerFactory.java
rename to httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalClientProtocolNegotiationStarter.java
index 240eddd..2453214 100644
--- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalClientH2EventHandlerFactory.java
+++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalClientProtocolNegotiationStarter.java
@@ -32,6 +32,7 @@ import javax.net.ssl.SSLContext;
 import org.apache.hc.core5.http.config.CharCodingConfig;
 import org.apache.hc.core5.http.config.Http1Config;
 import org.apache.hc.core5.http.impl.HttpProcessors;
+import org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler;
 import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory;
 import org.apache.hc.core5.http.nio.AsyncPushConsumer;
 import org.apache.hc.core5.http.nio.HandlerFactory;
@@ -39,8 +40,12 @@ import org.apache.hc.core5.http.protocol.HttpProcessor;
 import org.apache.hc.core5.http2.HttpVersionPolicy;
 import org.apache.hc.core5.http2.config.H2Config;
 import org.apache.hc.core5.http2.impl.H2Processors;
+import org.apache.hc.core5.http2.impl.nio.ClientH2PrefaceHandler;
 import org.apache.hc.core5.http2.impl.nio.ClientH2StreamMultiplexerFactory;
-import org.apache.hc.core5.http2.impl.nio.ClientHttpProtocolNegotiator;
+import org.apache.hc.core5.http2.impl.nio.ClientH2UpgradeHandler;
+import org.apache.hc.core5.http2.impl.nio.ClientHttp1UpgradeHandler;
+import org.apache.hc.core5.http2.impl.nio.HttpProtocolNegotiator;
+import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
 import org.apache.hc.core5.reactor.IOEventHandler;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
 import org.apache.hc.core5.reactor.ProtocolIOSession;
@@ -48,7 +53,7 @@ import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer;
 import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier;
 import org.apache.hc.core5.util.Args;
 
-class InternalClientH2EventHandlerFactory implements IOEventHandlerFactory {
+class InternalClientProtocolNegotiationStarter implements IOEventHandlerFactory {
 
     private final HttpProcessor httpProcessor;
     private final HandlerFactory<AsyncPushConsumer> exchangeHandlerFactory;
@@ -60,7 +65,7 @@ class InternalClientH2EventHandlerFactory implements IOEventHandlerFactory {
     private final SSLSessionInitializer sslSessionInitializer;
     private final SSLSessionVerifier sslSessionVerifier;
 
-    InternalClientH2EventHandlerFactory(
+    InternalClientProtocolNegotiationStarter(
             final HttpProcessor httpProcessor,
             final HandlerFactory<AsyncPushConsumer> exchangeHandlerFactory,
             final HttpVersionPolicy versionPolicy,
@@ -97,11 +102,17 @@ class InternalClientH2EventHandlerFactory implements IOEventHandlerFactory {
                 h2Config,
                 charCodingConfig,
                 LoggingH2StreamListener.INSTANCE);
-        return new ClientHttpProtocolNegotiator(
-                        ioSession,
-                        http1StreamHandlerFactory,
-                        http2StreamHandlerFactory,
-                        versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE);
+        ioSession.registerProtocol(ApplicationProtocol.HTTP_1_1.id, new ClientHttp1UpgradeHandler(http1StreamHandlerFactory));
+        ioSession.registerProtocol(ApplicationProtocol.HTTP_2.id, new ClientH2UpgradeHandler(http2StreamHandlerFactory));
+
+        switch (versionPolicy) {
+            case FORCE_HTTP_2:
+                return new ClientH2PrefaceHandler(ioSession, http2StreamHandlerFactory, false);
+            case FORCE_HTTP_1:
+                return new ClientHttp1IOEventHandler(http1StreamHandlerFactory.create(ioSession));
+            default:
+                return new HttpProtocolNegotiator(ioSession, null);
+        }
    }
 
 }
diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerH2EventHandlerFactory.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerProtocolNegotiationStarter.java
similarity index 78%
rename from httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerH2EventHandlerFactory.java
rename to httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerProtocolNegotiationStarter.java
index 6aefbf0..dccf104 100644
--- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerH2EventHandlerFactory.java
+++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerProtocolNegotiationStarter.java
@@ -29,9 +29,11 @@ package org.apache.hc.core5.testing.nio;
 
 import javax.net.ssl.SSLContext;
 
+import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.config.CharCodingConfig;
 import org.apache.hc.core5.http.config.Http1Config;
 import org.apache.hc.core5.http.impl.HttpProcessors;
+import org.apache.hc.core5.http.impl.nio.ServerHttp1IOEventHandler;
 import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.HandlerFactory;
@@ -39,8 +41,12 @@ import org.apache.hc.core5.http.protocol.HttpProcessor;
 import org.apache.hc.core5.http2.HttpVersionPolicy;
 import org.apache.hc.core5.http2.config.H2Config;
 import org.apache.hc.core5.http2.impl.H2Processors;
+import org.apache.hc.core5.http2.impl.nio.HttpProtocolNegotiator;
+import org.apache.hc.core5.http2.impl.nio.ServerH2PrefaceHandler;
 import org.apache.hc.core5.http2.impl.nio.ServerH2StreamMultiplexerFactory;
-import org.apache.hc.core5.http2.impl.nio.ServerHttpProtocolNegotiator;
+import org.apache.hc.core5.http2.impl.nio.ServerH2UpgradeHandler;
+import org.apache.hc.core5.http2.impl.nio.ServerHttp1UpgradeHandler;
+import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
 import org.apache.hc.core5.reactor.IOEventHandler;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
 import org.apache.hc.core5.reactor.ProtocolIOSession;
@@ -48,7 +54,7 @@ import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer;
 import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier;
 import org.apache.hc.core5.util.Args;
 
-class InternalServerH2EventHandlerFactory implements IOEventHandlerFactory {
+class InternalServerProtocolNegotiationStarter implements IOEventHandlerFactory {
 
     private final HttpProcessor httpProcessor;
     private final HandlerFactory<AsyncServerExchangeHandler> exchangeHandlerFactory;
@@ -60,7 +66,7 @@ class InternalServerH2EventHandlerFactory implements IOEventHandlerFactory {
     private final SSLSessionInitializer sslSessionInitializer;
     private final SSLSessionVerifier sslSessionVerifier;
 
-    public InternalServerH2EventHandlerFactory(
+    public InternalServerProtocolNegotiationStarter(
             final HttpProcessor httpProcessor,
             final HandlerFactory<AsyncServerExchangeHandler> exchangeHandlerFactory,
             final HttpVersionPolicy versionPolicy,
@@ -98,11 +104,19 @@ class InternalServerH2EventHandlerFactory implements IOEventHandlerFactory {
                 h2Config,
                 charCodingConfig,
                 LoggingH2StreamListener.INSTANCE);
-        return new ServerHttpProtocolNegotiator(
-                        ioSession,
-                        http1StreamHandlerFactory,
-                        http2StreamHandlerFactory,
-                        versionPolicy);
+        ioSession.registerProtocol(ApplicationProtocol.HTTP_1_1.id, new ServerHttp1UpgradeHandler(http1StreamHandlerFactory));
+        ioSession.registerProtocol(ApplicationProtocol.HTTP_2.id, new ServerH2UpgradeHandler(http2StreamHandlerFactory));
+
+        switch (versionPolicy) {
+            case FORCE_HTTP_2:
+                return new ServerH2PrefaceHandler(ioSession, http2StreamHandlerFactory);
+            case FORCE_HTTP_1:
+                return new ServerHttp1IOEventHandler(http1StreamHandlerFactory.create(
+                        sslContext != null ? URIScheme.HTTPS.id : URIScheme.HTTP.id,
+                        ioSession));
+            default:
+                return new HttpProtocolNegotiator(ioSession, null);
+        }
     }
 
 }
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..7454a35 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,8 @@
 
 package org.apache.hc.core5.http.impl.nio;
 
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.net.InetAddressUtils;
 
 /**
@@ -54,5 +56,11 @@ public class ClientHttp1IOEventHandler extends AbstractHttp1IOEventHandler {
         return buf.toString();
     }
 
+    @Override
+    public ProtocolVersion getProtocolVersion() {
+        final ProtocolVersion protocolVersion = super.getProtocolVersion();
+        return protocolVersion != null ? protocolVersion : HttpVersion.HTTP_1_1;
+    }
+
 }
 
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 2398e3d..28b3052 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,6 +27,8 @@
 
 package org.apache.hc.core5.http.impl.nio;
 
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.net.InetAddressUtils;
 
 /**
@@ -54,4 +56,10 @@ public class ServerHttp1IOEventHandler extends AbstractHttp1IOEventHandler {
         return buf.toString();
     }
 
+    @Override
+    public ProtocolVersion getProtocolVersion() {
+        final ProtocolVersion protocolVersion = super.getProtocolVersion();
+        return protocolVersion != null ? protocolVersion : HttpVersion.HTTP_1_1;
+    }
+
 }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/InternalDataChannel.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/InternalDataChannel.java
index b069cc3..f28da03 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/InternalDataChannel.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/InternalDataChannel.java
@@ -68,6 +68,7 @@ final class InternalDataChannel extends InternalChannel implements ProtocolIOSes
     private final Queue<InternalDataChannel> closedSessions;
     private final AtomicReference<SSLIOSession> tlsSessionRef;
     private final AtomicReference<IOSession> currentSessionRef;
+    private final AtomicReference<IOEventHandler> eventHandlerRef;
     private final ConcurrentMap<String, ProtocolUpgradeHandler> protocolUpgradeHandlerMap;
     private final AtomicBoolean closed;
 
@@ -85,6 +86,7 @@ final class InternalDataChannel extends InternalChannel implements ProtocolIOSes
         this.tlsSessionRef = new AtomicReference<>();
         this.currentSessionRef = new AtomicReference<>(
                 ioSessionDecorator != null ? ioSessionDecorator.decorate(ioSession) : ioSession);
+        this.eventHandlerRef = new AtomicReference<>();
         this.protocolUpgradeHandlerMap = new ConcurrentHashMap<>();
         this.closed = new AtomicBoolean(false);
     }
@@ -101,14 +103,14 @@ final class InternalDataChannel extends InternalChannel implements ProtocolIOSes
 
     @Override
     public IOEventHandler getHandler() {
-        final IOSession currentSession = currentSessionRef.get();
-        return currentSession.getHandler();
+        return eventHandlerRef.get();
     }
 
     @Override
     public void upgrade(final IOEventHandler handler) {
         final IOSession currentSession = currentSessionRef.get();
         currentSession.upgrade(handler);
+        eventHandlerRef.set(handler);
     }
 
     private IOEventHandler ensureHandler(final IOSession session) {

[httpcomponents-core] 02/03: Test coverage

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 6abb74e8dc0c83df7ebc050ce42d40edbe64cc96
Author: Arturo Bernal <ar...@gmail.com>
AuthorDate: Sun Dec 5 21:30:58 2021 +0100

    Test coverage
---
 .../apache/hc/core5/http2/config/H2ConfigTest.java |  90 ++++++++
 .../core5/reactive/TestReactiveEntityProducer.java | 117 ++++++++++
 .../ThreadingBehaviorTest.java}                    |  55 ++---
 ...Callback.java => DefaultThreadFactoryTest.java} |  57 ++---
 .../hc/core5/concurrent/TestBasicFuture.java       |  41 ++--
 ...utureCallback.java => TestCompletedFuture.java} |  56 ++---
 .../core5/http/config/TestNamedElementChain.java   |  26 ++-
 .../core5/http/impl/IncomingEntityDetailsTest.java |  96 +++++++++
 .../impl/io/TestDefaultBHttpServerConnection.java  |   6 +
 .../http/io/support/ClassicRequestBuilderTest.java | 239 +++++++++++++++++++++
 .../io/support/ClassicResponseBuilderTest.java     | 153 +++++++++++++
 .../core5/http/message/HttpRequestWrapperTest.java | 162 ++++++++++++++
 .../http/message/HttpResponseWrapperTest.java      |  85 ++++++++
 .../apache/hc/core5/http/ssl/TestTlsCiphers.java   |  54 +++++
 .../org/apache/hc/core5/pool/TestLaxConnPool.java  |  21 ++
 .../org/apache/hc/core5/ssl/SSLContextsTest.java   |  88 ++++++++
 16 files changed, 1194 insertions(+), 152 deletions(-)

diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/H2ConfigTest.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/H2ConfigTest.java
new file mode 100644
index 0000000..39e2b9d
--- /dev/null
+++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/H2ConfigTest.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.http2.config;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class H2ConfigTest {
+
+    @Test
+    void builder() {
+        // Create and start requester
+        final H2Config h2Config = H2Config.custom()
+                .setPushEnabled(false)
+                .build();
+        assertNotNull(h2Config);
+    }
+
+    @Test
+    void checkValues() {
+        // Create and start requester
+        final H2Config h2Config = H2Config.custom()
+                .setHeaderTableSize(1)
+                .setMaxConcurrentStreams(1)
+                .setMaxFrameSize(16384)
+                .setPushEnabled(true)
+                .setCompressionEnabled(true)
+                .build();
+
+        assertEquals(1, h2Config.getHeaderTableSize());
+        assertEquals(1, h2Config.getMaxConcurrentStreams());
+        assertEquals(16384, h2Config.getMaxFrameSize());
+        assertTrue(h2Config.isPushEnabled());
+        assertTrue(h2Config.isCompressionEnabled());
+    }
+
+    @Test
+    void copy() {
+        // Create and start requester
+        final H2Config h2Config = H2Config.custom()
+                .setHeaderTableSize(1)
+                .setMaxConcurrentStreams(1)
+                .setMaxFrameSize(16384)
+                .setPushEnabled(true)
+                .setCompressionEnabled(true)
+                .build();
+
+        final H2Config.Builder builder = H2Config.copy(h2Config);
+        final H2Config h2Config2 = builder.build();
+
+        assertAll(
+                () -> assertEquals(h2Config.getHeaderTableSize(), h2Config2.getHeaderTableSize()),
+                () -> assertEquals(h2Config.getInitialWindowSize(), h2Config2.getInitialWindowSize()),
+                () -> assertEquals(h2Config.getMaxConcurrentStreams(), h2Config2.getMaxConcurrentStreams()),
+                () -> assertEquals(h2Config.getMaxFrameSize(), h2Config2.getMaxFrameSize()),
+                () -> assertEquals(h2Config.getMaxHeaderListSize(), h2Config2.getMaxHeaderListSize())
+        );
+
+    }
+
+}
\ No newline at end of file
diff --git a/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveEntityProducer.java b/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveEntityProducer.java
new file mode 100644
index 0000000..369df16
--- /dev/null
+++ b/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveEntityProducer.java
@@ -0,0 +1,117 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.core5.reactive;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpStreamResetException;
+import org.apache.hc.core5.http.nio.DataStreamChannel;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import io.reactivex.Flowable;
+
+public class TestReactiveEntityProducer {
+
+    private static final long CONTENT_LENGTH = 1;
+    private static final ContentType CONTENT_TYPE = ContentType.APPLICATION_JSON;
+    private static final String GZIP_CONTENT_ENCODING = "gzip";
+
+    @Test
+    public void testStreamThatEndsNormally() throws Exception {
+        final Flowable<ByteBuffer> publisher = Flowable.just(
+                ByteBuffer.wrap(new byte[]{'1', '2', '3'}),
+                ByteBuffer.wrap(new byte[]{'4', '5', '6'}));
+        final ReactiveEntityProducer entityProducer = new ReactiveEntityProducer(publisher, CONTENT_LENGTH, CONTENT_TYPE, GZIP_CONTENT_ENCODING);
+
+        final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024);
+        final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel);
+
+        entityProducer.produce(streamChannel);
+
+        Assertions.assertTrue(byteChannel.isOpen(), "Should be open");
+        Assertions.assertEquals("123456", byteChannel.dump(StandardCharsets.US_ASCII));
+
+        entityProducer.produce(streamChannel);
+
+        Assertions.assertFalse(byteChannel.isOpen(), "Should be closed");
+        Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII));
+        Assertions.assertFalse(entityProducer.isChunked());
+        Assertions.assertEquals(GZIP_CONTENT_ENCODING, entityProducer.getContentEncoding());
+        Assertions.assertNull(entityProducer.getTrailerNames());
+        Assertions.assertEquals(CONTENT_LENGTH, entityProducer.getContentLength());
+        Assertions.assertEquals(CONTENT_TYPE.toString(), entityProducer.getContentType());
+        Assertions.assertFalse(entityProducer.isRepeatable());
+        Assertions.assertEquals(1, entityProducer.available());
+
+        entityProducer.releaseResources();
+    }
+
+    @Test
+
+    public void testStreamThatEndsWithError() throws Exception {
+        final Flowable<ByteBuffer> publisher = Flowable.concatArray(
+                Flowable.just(
+                        ByteBuffer.wrap(new byte[]{'1'}),
+                        ByteBuffer.wrap(new byte[]{'2'}),
+                        ByteBuffer.wrap(new byte[]{'3'}),
+                        ByteBuffer.wrap(new byte[]{'4'}),
+                        ByteBuffer.wrap(new byte[]{'5'}),
+                        ByteBuffer.wrap(new byte[]{'6'})),
+                Flowable.error(new RuntimeException())
+        );
+        final ReactiveEntityProducer entityProducer = new ReactiveEntityProducer(publisher, CONTENT_LENGTH, CONTENT_TYPE, GZIP_CONTENT_ENCODING);
+
+        final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024);
+        final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel);
+
+        entityProducer.produce(streamChannel);
+        Assertions.assertEquals("12345", byteChannel.dump(StandardCharsets.US_ASCII));
+
+        final HttpStreamResetException exception = Assertions.assertThrows(HttpStreamResetException.class, () ->
+                entityProducer.produce(streamChannel));
+        Assertions.assertTrue(exception.getCause() instanceof RuntimeException, "Expected published exception to be rethrown");
+        Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII));
+        entityProducer.failed(exception);
+        Assertions.assertEquals(1, entityProducer.available());
+
+        Assertions.assertTrue(byteChannel.isOpen());
+        Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII));
+        Assertions.assertFalse(entityProducer.isChunked());
+        Assertions.assertEquals(GZIP_CONTENT_ENCODING, entityProducer.getContentEncoding());
+        Assertions.assertNull(entityProducer.getTrailerNames());
+        Assertions.assertEquals(CONTENT_LENGTH, entityProducer.getContentLength());
+        Assertions.assertEquals(CONTENT_TYPE.toString(), entityProducer.getContentType());
+        Assertions.assertFalse(entityProducer.isRepeatable());
+        Assertions.assertEquals(1, entityProducer.available());
+
+        entityProducer.releaseResources();
+    }
+
+}
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/BasicFutureCallback.java b/httpcore5/src/test/java/org/apache/hc/core5/annotation/ThreadingBehaviorTest.java
similarity index 57%
copy from httpcore5/src/test/java/org/apache/hc/core5/concurrent/BasicFutureCallback.java
copy to httpcore5/src/test/java/org/apache/hc/core5/annotation/ThreadingBehaviorTest.java
index 44236dc..2ed0b14 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/BasicFutureCallback.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/annotation/ThreadingBehaviorTest.java
@@ -24,52 +24,23 @@
  * <http://www.apache.org/>.
  *
  */
-package org.apache.hc.core5.concurrent;
 
+package org.apache.hc.core5.annotation;
 
-class BasicFutureCallback<T> implements FutureCallback<T> {
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
-    private T result;
-    private Exception ex;
-    private boolean completed;
-    private boolean failed;
-    private boolean cancelled;
+import org.junit.jupiter.api.Test;
 
-    @Override
-    public void completed(final T result) {
-        this.result = result;
-        this.completed = true;
-    }
-
-    public T getResult() {
-        return this.result;
-    }
-
-    public Exception getException() {
-        return this.ex;
-    }
-
-    @Override
-    public void failed(final Exception ex) {
-        this.ex = ex;
-        this.failed = true;
-    }
-
-    @Override
-    public void cancelled() {
-        this.cancelled = true;
-    }
-
-    public boolean isCompleted() {
-        return this.completed;
-    }
-
-    public boolean isFailed() {
-        return this.failed;
-    }
+public class ThreadingBehaviorTest {
 
-    public boolean isCancelled() {
-        return this.cancelled;
+    @Test
+    void testName(){
+        assertEquals("SAFE", ThreadingBehavior.SAFE.name());
+        assertEquals("SAFE_CONDITIONAL", ThreadingBehavior.SAFE_CONDITIONAL.name());
+        assertEquals("IMMUTABLE_CONDITIONAL", ThreadingBehavior.IMMUTABLE_CONDITIONAL.name());
+        assertEquals("IMMUTABLE", ThreadingBehavior.IMMUTABLE.name());
+        assertEquals("STATELESS", ThreadingBehavior.STATELESS.name());
+        assertEquals("UNSAFE", ThreadingBehavior.UNSAFE.name());
     }
 
-}
+}
\ No newline at end of file
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/BasicFutureCallback.java b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/DefaultThreadFactoryTest.java
similarity index 58%
copy from httpcore5/src/test/java/org/apache/hc/core5/concurrent/BasicFutureCallback.java
copy to httpcore5/src/test/java/org/apache/hc/core5/concurrent/DefaultThreadFactoryTest.java
index 44236dc..7ffc392 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/BasicFutureCallback.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/DefaultThreadFactoryTest.java
@@ -26,50 +26,23 @@
  */
 package org.apache.hc.core5.concurrent;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
 
-class BasicFutureCallback<T> implements FutureCallback<T> {
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
 
-    private T result;
-    private Exception ex;
-    private boolean completed;
-    private boolean failed;
-    private boolean cancelled;
+public class DefaultThreadFactoryTest {
 
-    @Override
-    public void completed(final T result) {
-        this.result = result;
-        this.completed = true;
+    @Test
+    void newThread() throws Exception {
+        final ThreadFactory defaultThreadFactory = new DefaultThreadFactory("I/O server dispatch", true);
+        final CountDownLatch lockHeld = new CountDownLatch(1);
+        final Thread thread = defaultThreadFactory.newThread(lockHeld::countDown);
+        Assertions.assertNotNull(thread);
+        thread.start();
+        Assertions.assertTrue(lockHeld.await(100, TimeUnit.MILLISECONDS));
     }
 
-    public T getResult() {
-        return this.result;
-    }
-
-    public Exception getException() {
-        return this.ex;
-    }
-
-    @Override
-    public void failed(final Exception ex) {
-        this.ex = ex;
-        this.failed = true;
-    }
-
-    @Override
-    public void cancelled() {
-        this.cancelled = true;
-    }
-
-    public boolean isCompleted() {
-        return this.completed;
-    }
-
-    public boolean isFailed() {
-        return this.failed;
-    }
-
-    public boolean isCancelled() {
-        return this.cancelled;
-    }
-
-}
+}
\ No newline at end of file
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestBasicFuture.java b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestBasicFuture.java
index f901b0a..a95e3a8 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestBasicFuture.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestBasicFuture.java
@@ -34,12 +34,13 @@ import java.util.concurrent.TimeoutException;
 import org.apache.hc.core5.util.TimeoutValueException;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
 
 public class TestBasicFuture {
 
     @Test
     public void testCompleted() throws Exception {
-        final BasicFutureCallback<Object> callback = new BasicFutureCallback<>();
+        final FutureCallback<Object> callback = Mockito.mock(FutureCallback.class);
         final BasicFuture<Object> future = new BasicFuture<>(callback);
 
         Assertions.assertFalse(future.isDone());
@@ -48,11 +49,9 @@ public class TestBasicFuture {
         final Exception boom = new Exception();
         future.completed(result);
         future.failed(boom);
-        Assertions.assertTrue(callback.isCompleted());
-        Assertions.assertSame(result, callback.getResult());
-        Assertions.assertFalse(callback.isFailed());
-        Assertions.assertNull(callback.getException());
-        Assertions.assertFalse(callback.isCancelled());
+        Mockito.verify(callback).completed(result);
+        Mockito.verify(callback, Mockito.never()).failed(Mockito.any());
+        Mockito.verify(callback, Mockito.never()).cancelled();
 
         Assertions.assertSame(result, future.get());
         Assertions.assertTrue(future.isDone());
@@ -62,7 +61,7 @@ public class TestBasicFuture {
 
     @Test
     public void testCompletedWithTimeout() throws Exception {
-        final BasicFutureCallback<Object> callback = new BasicFutureCallback<>();
+        final FutureCallback<Object> callback = Mockito.mock(FutureCallback.class);
         final BasicFuture<Object> future = new BasicFuture<>(callback);
 
         Assertions.assertFalse(future.isDone());
@@ -71,11 +70,9 @@ public class TestBasicFuture {
         final Exception boom = new Exception();
         future.completed(result);
         future.failed(boom);
-        Assertions.assertTrue(callback.isCompleted());
-        Assertions.assertSame(result, callback.getResult());
-        Assertions.assertFalse(callback.isFailed());
-        Assertions.assertNull(callback.getException());
-        Assertions.assertFalse(callback.isCancelled());
+        Mockito.verify(callback).completed(result);
+        Mockito.verify(callback, Mockito.never()).failed(Mockito.any());
+        Mockito.verify(callback, Mockito.never()).cancelled();
 
         Assertions.assertSame(result, future.get(1, TimeUnit.MILLISECONDS));
         Assertions.assertTrue(future.isDone());
@@ -84,17 +81,15 @@ public class TestBasicFuture {
 
     @Test
     public void testFailed() throws Exception {
-        final BasicFutureCallback<Object> callback = new BasicFutureCallback<>();
+        final FutureCallback<Object> callback = Mockito.mock(FutureCallback.class);
         final BasicFuture<Object> future = new BasicFuture<>(callback);
         final Object result = new Object();
         final Exception boom = new Exception();
         future.failed(boom);
         future.completed(result);
-        Assertions.assertFalse(callback.isCompleted());
-        Assertions.assertNull(callback.getResult());
-        Assertions.assertTrue(callback.isFailed());
-        Assertions.assertSame(boom, callback.getException());
-        Assertions.assertFalse(callback.isCancelled());
+        Mockito.verify(callback, Mockito.never()).completed(Mockito.any());
+        Mockito.verify(callback).failed(boom);
+        Mockito.verify(callback, Mockito.never()).cancelled();
 
         try {
             future.get();
@@ -107,18 +102,16 @@ public class TestBasicFuture {
 
     @Test
     public void testCancelled() throws Exception {
-        final BasicFutureCallback<Object> callback = new BasicFutureCallback<>();
+        final FutureCallback<Object> callback = Mockito.mock(FutureCallback.class);
         final BasicFuture<Object> future = new BasicFuture<>(callback);
         final Object result = new Object();
         final Exception boom = new Exception();
         future.cancel(true);
         future.failed(boom);
         future.completed(result);
-        Assertions.assertFalse(callback.isCompleted());
-        Assertions.assertNull(callback.getResult());
-        Assertions.assertFalse(callback.isFailed());
-        Assertions.assertNull(callback.getException());
-        Assertions.assertTrue(callback.isCancelled());
+        Mockito.verify(callback, Mockito.never()).completed(Mockito.any());
+        Mockito.verify(callback, Mockito.never()).failed(Mockito.any());
+        Mockito.verify(callback).cancelled();
 
         Assertions.assertThrows(CancellationException.class, future::get);
         Assertions.assertTrue(future.isDone());
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/BasicFutureCallback.java b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestCompletedFuture.java
similarity index 58%
rename from httpcore5/src/test/java/org/apache/hc/core5/concurrent/BasicFutureCallback.java
rename to httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestCompletedFuture.java
index 44236dc..93768fb 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/BasicFutureCallback.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestCompletedFuture.java
@@ -26,50 +26,20 @@
  */
 package org.apache.hc.core5.concurrent;
 
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
 
-class BasicFutureCallback<T> implements FutureCallback<T> {
+public class TestCompletedFuture {
 
-    private T result;
-    private Exception ex;
-    private boolean completed;
-    private boolean failed;
-    private boolean cancelled;
-
-    @Override
-    public void completed(final T result) {
-        this.result = result;
-        this.completed = true;
-    }
-
-    public T getResult() {
-        return this.result;
-    }
-
-    public Exception getException() {
-        return this.ex;
-    }
-
-    @Override
-    public void failed(final Exception ex) {
-        this.ex = ex;
-        this.failed = true;
-    }
-
-    @Override
-    public void cancelled() {
-        this.cancelled = true;
-    }
-
-    public boolean isCompleted() {
-        return this.completed;
-    }
-
-    public boolean isFailed() {
-        return this.failed;
-    }
-
-    public boolean isCancelled() {
-        return this.cancelled;
+    @Test
+    public void testCompleted() {
+        final Object result = new Object();
+        final CompletedFuture<Object> future = new CompletedFuture<>(result);
+        Assertions.assertSame(result, future.get());
+        Assertions.assertTrue(future.isDone());
+        Assertions.assertFalse(future.isCancelled());
+        future.cancel(true);
+        future.cancel();
     }
 
-}
+}
\ No newline at end of file
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/config/TestNamedElementChain.java b/httpcore5/src/test/java/org/apache/hc/core5/http/config/TestNamedElementChain.java
index 68683b6..e64fca9 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/config/TestNamedElementChain.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/config/TestNamedElementChain.java
@@ -28,6 +28,10 @@
 package org.apache.hc.core5.http.config;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.CoreMatchers.is;
 
 import org.hamcrest.CoreMatchers;
 import org.junit.jupiter.api.Test;
@@ -95,4 +99,24 @@ public class TestNamedElementChain {
         assertThat(list.getSize(), CoreMatchers.equalTo(2));
     }
 
-}
\ No newline at end of file
+    @Test
+    public void testFind() {
+        final NamedElementChain<Character> list = new NamedElementChain<>();
+        list.addLast('c', "c");
+        assertThat(list.find("c"), notNullValue());
+        assertThat(list.find("a"), nullValue());
+    }
+
+    @Test
+    public void testReplace() {
+        final NamedElementChain<Character> list = new NamedElementChain<>();
+        list.addLast('c', "c");
+        final boolean found = list.replace("c",'z' );
+        assertThat(found, is(true));
+        assertThat(list.find("c").getValue(), equalTo('z'));
+        assertThat(list.find("c").getName(), equalTo("c"));
+        final boolean notFound = list.replace("X",'z' );
+        assertThat(notFound, is(false));
+
+    }
+}
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/IncomingEntityDetailsTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/IncomingEntityDetailsTest.java
new file mode 100644
index 0000000..71204ae
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/IncomingEntityDetailsTest.java
@@ -0,0 +1,96 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists 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;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.MessageHeaders;
+import org.apache.hc.core5.http.message.BasicHeader;
+import org.apache.hc.core5.http.message.HeaderGroup;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class IncomingEntityDetailsTest {
+
+    @Test
+    public void getContentLengthEmpty() {
+        final MessageHeaders messageHeaders = new HeaderGroup();
+        final IncomingEntityDetails incomingEntityDetails = new IncomingEntityDetails(messageHeaders);
+        assertAll(
+                () -> assertEquals(-1, incomingEntityDetails.getContentLength()),
+                () -> assertNull(incomingEntityDetails.getContentType()),
+                () -> assertNull(incomingEntityDetails.getContentEncoding()),
+                () -> assertEquals(incomingEntityDetails.getTrailerNames().size(), 0)
+        );
+    }
+
+    @Test
+    public void messageHeadersNull() {
+        Assertions.assertThrows(NullPointerException.class, () -> new IncomingEntityDetails(null),
+                "Message Header Null");
+    }
+
+    @Test
+    public void getContentLength() {
+        final MessageHeaders messageHeaders = new HeaderGroup();
+        final HeaderGroup headerGroup = new HeaderGroup();
+        final Header header = new BasicHeader("name", "value");
+        headerGroup.addHeader(header);
+        final IncomingEntityDetails incomingEntityDetails = new IncomingEntityDetails(messageHeaders);
+        assertAll(
+                () -> assertEquals(-1, incomingEntityDetails.getContentLength()),
+                () -> assertTrue(incomingEntityDetails.isChunked())
+        );
+    }
+
+    @Test
+    public void getTrailerNames() {
+        final HeaderGroup messageHeaders = new HeaderGroup();
+        final Header header = new BasicHeader(HttpHeaders.TRAILER, "a, b, c, c");
+        messageHeaders.setHeaders(header);
+        final IncomingEntityDetails incomingEntityDetails = new IncomingEntityDetails(messageHeaders);
+        final Set<String> incomingSet = incomingEntityDetails.getTrailerNames();
+        assertAll(
+                () -> assertFalse(incomingSet.isEmpty()),
+                () -> assertTrue(incomingSet.containsAll(Stream.of("a", "b", "c")
+                        .collect(Collectors.toCollection(HashSet::new))))
+        );
+    }
+
+}
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpServerConnection.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpServerConnection.java
index 743098a..78fb072 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpServerConnection.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpServerConnection.java
@@ -43,6 +43,7 @@ import org.apache.hc.core5.http.config.Http1Config;
 import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy;
 import org.apache.hc.core5.http.io.entity.StringEntity;
 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
+import org.apache.hc.core5.util.Timeout;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -90,6 +91,11 @@ public class TestDefaultBHttpServerConnection {
         Assertions.assertEquals(Method.GET.name(), request.getMethod());
         Assertions.assertTrue(request.containsHeader("User-Agent"));
         Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount());
+        Assertions.assertNull(conn.getEndpointDetails().getRemoteAddress());
+        Assertions.assertNull(conn.getEndpointDetails().getLocalAddress());
+
+        Assertions.assertEquals(Timeout.ofMilliseconds(0), conn.getEndpointDetails().getSocketTimeout());
+        Assertions.assertEquals("null<->null", conn.getEndpointDetails().toString());
     }
 
     @Test
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilderTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilderTest.java
new file mode 100644
index 0000000..33a1f9e
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilderTest.java
@@ -0,0 +1,239 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists 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.support;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.Method;
+import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.message.BasicHeader;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.apache.hc.core5.net.URIAuthority;
+import org.apache.hc.core5.net.URIBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class ClassicRequestBuilderTest {
+
+    @Test
+    void constructor() throws UnknownHostException, URISyntaxException {
+        final ClassicRequestBuilder classicRequestBuilder = new ClassicRequestBuilder(Method.HEAD);
+        assertEquals(Method.HEAD.name(), classicRequestBuilder.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder2 = new ClassicRequestBuilder(Method.HEAD.name());
+        assertEquals(Method.HEAD.name(), classicRequestBuilder2.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder3 = new ClassicRequestBuilder(Method.HEAD.name(), URIBuilder.localhost().build());
+        assertEquals(Method.HEAD.name(), classicRequestBuilder3.getMethod());
+        assertEquals(URIBuilder.localhost().getHost(), classicRequestBuilder3.getAuthority().getHostName());
+
+        final ClassicRequestBuilder classicRequestBuilder4 = new ClassicRequestBuilder(Method.HEAD, URIBuilder.localhost().build());
+        assertEquals(Method.HEAD.name(), classicRequestBuilder4.getMethod());
+        assertEquals(URIBuilder.localhost().getHost(), classicRequestBuilder4.getAuthority().getHostName());
+
+        final ClassicRequestBuilder classicRequestBuilder5 = new ClassicRequestBuilder(Method.HEAD, "/localhost");
+        assertEquals(Method.HEAD.name(), classicRequestBuilder5.getMethod());
+        assertEquals("/localhost", classicRequestBuilder5.getPath());
+
+        final ClassicRequestBuilder classicRequestBuilder6 = new ClassicRequestBuilder(Method.HEAD.name(), "/localhost");
+        assertEquals(Method.HEAD.name(), classicRequestBuilder6.getMethod());
+        assertEquals("/localhost", classicRequestBuilder6.getPath());
+    }
+
+    @Test
+    public void create() {
+        final ClassicHttpRequest classicHttpRequest = ClassicRequestBuilder.create(Method.HEAD.name()).build();
+        assertEquals(Method.HEAD.name(), classicHttpRequest.getMethod());
+    }
+
+    @Test
+    public void get() throws UnknownHostException, URISyntaxException {
+        final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.get();
+        assertEquals(Method.GET.name(), classicRequestBuilder.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.get(URIBuilder.localhost().build());
+        assertEquals(Method.GET.name(), classicRequestBuilder1.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.get("/localhost");
+        assertEquals(Method.GET.name(), classicRequestBuilder3.getMethod());
+        assertEquals("/localhost", classicRequestBuilder3.getPath());
+    }
+
+    @Test
+    public void head() throws UnknownHostException, URISyntaxException {
+        final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.head();
+        assertEquals(Method.HEAD.name(), classicRequestBuilder.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.head(URIBuilder.localhost().build());
+        assertEquals(Method.HEAD.name(), classicRequestBuilder1.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.head("/localhost");
+        assertEquals(Method.HEAD.name(), classicRequestBuilder3.getMethod());
+        assertEquals("/localhost", classicRequestBuilder3.getPath());
+    }
+
+    @Test
+    public void patch() throws UnknownHostException, URISyntaxException {
+        final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.patch();
+        assertEquals(Method.PATCH.name(), classicRequestBuilder.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.patch(URIBuilder.localhost().build());
+        assertEquals(Method.PATCH.name(), classicRequestBuilder1.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.patch("/localhost");
+        assertEquals(Method.PATCH.name(), classicRequestBuilder3.getMethod());
+        assertEquals("/localhost", classicRequestBuilder3.getPath());
+    }
+
+    @Test
+    public void post() throws UnknownHostException, URISyntaxException {
+        final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.post();
+        assertEquals(Method.POST.name(), classicRequestBuilder.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.post(URIBuilder.localhost().build());
+        assertEquals(Method.POST.name(), classicRequestBuilder1.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.post("/localhost");
+        assertEquals(Method.POST.name(), classicRequestBuilder3.getMethod());
+        assertEquals("/localhost", classicRequestBuilder3.getPath());
+    }
+
+    @Test
+    public void put() throws UnknownHostException, URISyntaxException {
+        final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.put();
+        assertEquals(Method.PUT.name(), classicRequestBuilder.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.put(URIBuilder.localhost().build());
+        assertEquals(Method.PUT.name(), classicRequestBuilder1.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.put("/localhost");
+        assertEquals(Method.PUT.name(), classicRequestBuilder3.getMethod());
+        assertEquals("/localhost", classicRequestBuilder3.getPath());
+    }
+
+    @Test
+    public void delete() throws UnknownHostException, URISyntaxException {
+        final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.delete();
+        assertEquals(Method.DELETE.name(), classicRequestBuilder.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.delete(URIBuilder.localhost().build());
+        assertEquals(Method.DELETE.name(), classicRequestBuilder1.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.delete("/localhost");
+        assertEquals(Method.DELETE.name(), classicRequestBuilder3.getMethod());
+        assertEquals("/localhost", classicRequestBuilder3.getPath());
+    }
+
+    @Test
+    public void trace() throws UnknownHostException, URISyntaxException {
+        final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.trace();
+        assertEquals(Method.TRACE.name(), classicRequestBuilder.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.trace(URIBuilder.localhost().build());
+        assertEquals(Method.TRACE.name(), classicRequestBuilder1.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.trace("/localhost");
+        assertEquals(Method.TRACE.name(), classicRequestBuilder3.getMethod());
+        assertEquals("/localhost", classicRequestBuilder3.getPath());
+    }
+
+    @Test
+    public void option() throws UnknownHostException, URISyntaxException {
+        final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.options();
+        assertEquals(Method.OPTIONS.name(), classicRequestBuilder.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.options(URIBuilder.localhost().build());
+        assertEquals(Method.OPTIONS.name(), classicRequestBuilder1.getMethod());
+
+        final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.options("/localhost");
+        assertEquals(Method.OPTIONS.name(), classicRequestBuilder3.getMethod());
+        assertEquals("/localhost", classicRequestBuilder3.getPath());
+    }
+
+    @Test
+    public void builder() {
+        final Header header = new BasicHeader("header2", "blah");
+        final ClassicHttpRequest classicHttpRequest = ClassicRequestBuilder.get()
+                .setVersion(HttpVersion.HTTP_1_1)
+                .setCharset(StandardCharsets.US_ASCII)
+                .setAuthority(new URIAuthority("host"))
+                .setEntity("<html><body><h1>Access denied</h1></body></html>", ContentType.TEXT_HTML)
+                .setHeader(new BasicHeader("header2", "blah"))
+                .setHeader("X-Test-Filter", "active")
+                .setHeader(header)
+                .setPath("path/")
+                .setScheme("http")
+                .addHeader(header)
+                .addHeader("header", ".addHeader(header)")
+                .addParameter(new BasicHeader("header2", "blah"))
+                .addParameter("param1", "value1")
+                .addParameters(new BasicNameValuePair("param3", "value3"), new BasicNameValuePair("param4", null))
+                .setAbsoluteRequestUri(true)
+                .setEntity(new StringEntity("requestBody"))
+                .setEntity(new ByteArrayEntity(new byte[10240], ContentType.TEXT_PLAIN))
+                .setEntity(new byte[10240], ContentType.TEXT_HTML)
+                .setEntity("requestBody")
+                .setUri("theUri")
+                .setHttpHost(new HttpHost("httpbin.org"))
+                .build();
+
+        assertAll("Should return address of Oracle's headquarter",
+                () -> assertNotNull(classicHttpRequest.getEntity()),
+                () -> assertEquals(Method.GET.name(), classicHttpRequest.getMethod()),
+                () -> assertEquals("http", classicHttpRequest.getScheme()),
+                () -> assertEquals("httpbin.org", classicHttpRequest.getAuthority().getHostName()),
+                () -> assertEquals(HttpVersion.HTTP_1_1, classicHttpRequest.getVersion()),
+                () -> assertEquals(4, classicHttpRequest.getHeaders().length),
+                () -> assertNotNull(ClassicRequestBuilder.get().toString()),
+                () -> assertEquals("http://httpbin.org/theUri?header2=blah&param1=value1&param3=value3&param4", new String(classicHttpRequest.getRequestUri().getBytes()))
+        );
+    }
+
+
+    @Test
+    public void builderTraceThrowsIllegalStateException() {
+        Assertions.assertThrows(IllegalStateException.class, () ->
+                ClassicRequestBuilder.trace()
+                        .setVersion(HttpVersion.HTTP_1_1)
+                        .setEntity(new ByteArrayEntity(new byte[10240], ContentType.TEXT_PLAIN))
+                        .build());
+    }
+
+}
\ No newline at end of file
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicResponseBuilderTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicResponseBuilderTest.java
new file mode 100644
index 0000000..32a3777
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicResponseBuilderTest.java
@@ -0,0 +1,153 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists 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.support;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.config.Http1Config;
+import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy;
+import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnection;
+import org.apache.hc.core5.http.impl.io.DefaultHttpRequestParserFactory;
+import org.apache.hc.core5.http.impl.io.DefaultHttpResponseWriterFactory;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.message.BasicHeader;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+class ClassicResponseBuilderTest {
+
+    @Mock
+    Socket socket;
+
+    DefaultBHttpServerConnection conn;
+
+    ByteArrayOutputStream outStream;
+
+    @BeforeEach
+    public void prepareMocks() throws IOException {
+        MockitoAnnotations.openMocks(this);
+        conn = new DefaultBHttpServerConnection("http", Http1Config.DEFAULT,
+                null, null,
+                DefaultContentLengthStrategy.INSTANCE,
+                DefaultContentLengthStrategy.INSTANCE,
+                DefaultHttpRequestParserFactory.INSTANCE,
+                DefaultHttpResponseWriterFactory.INSTANCE);
+        outStream = new ByteArrayOutputStream();
+        Mockito.when(socket.getOutputStream()).thenReturn(outStream);
+        conn.bind(socket);
+        Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount());
+    }
+
+    @Test
+    void create() throws IOException, HttpException {
+
+        final ClassicHttpResponse response = ClassicResponseBuilder.create(200)
+                .setHeader("X-Test-Filter", "active")
+                .addHeader("header1", "blah")
+                .setHeader(new BasicHeader("header2", "blah"))
+                .addHeader(new BasicHeader("header3", "blah"))
+                .setVersion(HttpVersion.HTTP_1_1)
+                .setEntity("<html><body><h1>Access OK</h1></body></html>", ContentType.TEXT_HTML)
+                .setEntity("Another entity")
+                .build();
+
+        response.addHeader("User-Agent", "test");
+
+        conn.sendResponseHeader(response);
+        conn.flush();
+
+        Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount());
+        final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII);
+        Assertions.assertEquals("HTTP/1.1 200 OK\r\nX-Test-Filter: active\r\nheader1: blah\r\nheader2: blah\r\nheader3: blah\r\nUser-Agent: test\r\n\r\n", s);
+        Assertions.assertNotNull(response.getEntity());
+    }
+
+    @Test
+    void remove() throws IOException, HttpException {
+        final Header header = new BasicHeader("header2", "blah");
+        final ClassicHttpResponse response = ClassicResponseBuilder.create(200)
+                .setEntity(new StringEntity("123", ContentType.TEXT_PLAIN))
+                .setHeader("X-Test-Filter", "active")
+                .addHeader("header1", "blah")
+                .setHeader(header)
+                .addHeader(new BasicHeader("header3", "blah"))
+                .setVersion(HttpVersion.HTTP_1_1)
+                .setEntity("<html><body><h1>Access OK</h1></body></html>", ContentType.TEXT_HTML)
+                .setEntity("Another entity")
+                .removeHeader(header)
+                .build();
+
+        response.removeHeaders("X-Test-Filter");
+
+        conn.sendResponseHeader(response);
+        conn.flush();
+
+        Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount());
+        final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII);
+        Assertions.assertEquals("HTTP/1.1 200 OK\r\nheader1: blah\r\nheader3: blah\r\n\r\n", s);
+        Assertions.assertNotNull(response.getEntity());
+
+    }
+
+    @Test
+    void copy() throws IOException, HttpException {
+        final Header header = new BasicHeader("header2", "blah");
+        final ClassicHttpResponse response = ClassicResponseBuilder.create(200)
+                .setEntity(new StringEntity("123", ContentType.TEXT_PLAIN))
+                .addHeader("header1", "blah")
+                .setHeader(header)
+                .addHeader(new BasicHeader("header3", "blah"))
+                .setVersion(HttpVersion.HTTP_1_1)
+                .setEntity("<html><body><h1>Access OK</h1></body></html>", ContentType.TEXT_HTML)
+                .setEntity("Another entity")
+                .removeHeader(header)
+                .build();
+
+        final ClassicResponseBuilder classicResponseBuilder = ClassicResponseBuilder.copy(response);
+        final ClassicHttpResponse response2 = classicResponseBuilder.build();
+        conn.sendResponseHeader(response2);
+        conn.flush();
+
+        Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount());
+        final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII);
+        Assertions.assertEquals("HTTP/1.1 200 OK\r\nheader1: blah\r\nheader3: blah\r\n\r\n", s);
+        Assertions.assertNotNull(response.getEntity());
+        Assertions.assertNotNull(classicResponseBuilder.toString());
+    }
+}
\ No newline at end of file
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpRequestWrapperTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpRequestWrapperTest.java
new file mode 100644
index 0000000..a1b0636
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpRequestWrapperTest.java
@@ -0,0 +1,162 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists 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.message;
+
+import java.net.URI;
+
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.Method;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.net.URIAuthority;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class HttpRequestWrapperTest {
+
+    @Test
+    public void testRequestBasics() throws Exception {
+        final HttpRequest request = new BasicHttpRequest(Method.GET, "/stuff");
+        final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request);
+
+        Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod());
+        Assertions.assertEquals("/stuff", httpRequestWrapper.getPath());
+        Assertions.assertNull(httpRequestWrapper.getAuthority());
+        Assertions.assertEquals(new URI("/stuff"), httpRequestWrapper.getUri());
+        httpRequestWrapper.setPath("/another-stuff");
+        Assertions.assertEquals("/another-stuff", httpRequestWrapper.getPath());
+    }
+
+    @Test
+    public void testDefaultRequestConstructors() {
+        final HttpRequest request1 = new BasicHttpRequest("WHATEVER", "/");
+        final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request1);
+        Assertions.assertEquals("WHATEVER", httpRequestWrapper.getMethod());
+        Assertions.assertEquals("/", httpRequestWrapper.getPath());
+
+        final HttpRequest request2 = new BasicHttpRequest(Method.GET, "/");
+        final HttpRequestWrapper httpRequestWrapper2 = new HttpRequestWrapper(request2);
+        Assertions.assertEquals(Method.GET.name(), httpRequestWrapper2.getMethod());
+        Assertions.assertEquals("/", httpRequestWrapper2.getPath());
+
+        Assertions.assertThrows(NullPointerException.class, () ->
+                new BasicHttpRequest(Method.GET, (URI) null));
+    }
+
+
+    @Test
+    public void testRequestWithRelativeURI() throws Exception {
+        final HttpRequest request = new BasicHttpRequest(Method.GET, new URI("/stuff"));
+        final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request);
+        Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod());
+        Assertions.assertEquals("/stuff", httpRequestWrapper.getPath());
+        Assertions.assertNull(httpRequestWrapper.getAuthority());
+        Assertions.assertEquals(new URI("/stuff"), httpRequestWrapper.getUri());
+    }
+
+    @Test
+    public void testRequestWithAbsoluteURI() throws Exception {
+        final HttpRequest request = new BasicHttpRequest(Method.GET, new URI("https://host:9443/stuff?param=value"));
+        final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request);
+        Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod());
+        Assertions.assertEquals("/stuff?param=value", httpRequestWrapper.getPath());
+        Assertions.assertEquals(new URIAuthority("host", 9443), httpRequestWrapper.getAuthority());
+        Assertions.assertEquals("https", httpRequestWrapper.getScheme());
+        Assertions.assertEquals(new URI("https://host:9443/stuff?param=value"), httpRequestWrapper.getUri());
+        httpRequestWrapper.setScheme((URIScheme.HTTP.id));
+        Assertions.assertEquals("http", httpRequestWrapper.getScheme());
+    }
+
+    @Test
+    public void testRequestWithAbsoluteURIAsPath() throws Exception {
+        final HttpRequest request = new BasicHttpRequest(Method.GET, "https://host:9443/stuff?param=value");
+        final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request);
+        Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod());
+        Assertions.assertEquals("/stuff?param=value", httpRequestWrapper.getPath());
+        Assertions.assertEquals(new URIAuthority("host", 9443), httpRequestWrapper.getAuthority());
+        Assertions.assertEquals("https", httpRequestWrapper.getScheme());
+        Assertions.assertEquals(new URI("https://host:9443/stuff?param=value"), httpRequestWrapper.getUri());
+    }
+
+    @Test
+    public void testRequestWithNoPath() throws Exception {
+        final HttpRequest request = new BasicHttpRequest(Method.GET, new URI("http://host"));
+        final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request);
+        Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod());
+        Assertions.assertEquals("/", httpRequestWrapper.getPath());
+        Assertions.assertEquals(new URIAuthority("host"), httpRequestWrapper.getAuthority());
+        Assertions.assertEquals("http", httpRequestWrapper.getScheme());
+        Assertions.assertEquals(new URI("http://host/"), httpRequestWrapper.getUri());
+    }
+
+    @Test
+    public void testRequestWithUserInfo() throws Exception {
+        final HttpRequest request = new BasicHttpRequest(Method.GET, new URI("https://user:pwd@host:9443/stuff?param=value"));
+        final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request);
+        Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod());
+        Assertions.assertEquals("/stuff?param=value", httpRequestWrapper.getPath());
+        Assertions.assertEquals(new URIAuthority("user:pwd", "host", 9443), httpRequestWrapper.getAuthority());
+        Assertions.assertEquals("https", httpRequestWrapper.getScheme());
+        Assertions.assertEquals(new URI("https://host:9443/stuff?param=value"), httpRequestWrapper.getUri());
+    }
+
+    @Test
+    public void testRequestWithAuthority() throws Exception {
+        final HttpRequest request = new BasicHttpRequest(Method.GET, new HttpHost("http", "somehost", -1), "/stuff");
+        final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request);
+        Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod());
+        Assertions.assertEquals("/stuff", httpRequestWrapper.getPath());
+        Assertions.assertEquals(new URIAuthority("somehost"), httpRequestWrapper.getAuthority());
+        Assertions.assertEquals(new URI("http://somehost/stuff"), httpRequestWrapper.getUri());
+
+        httpRequestWrapper.setAuthority(new URIAuthority("newHost"));
+        Assertions.assertEquals(new URIAuthority("newHost"), httpRequestWrapper.getAuthority());
+
+    }
+
+    @Test
+    public void testRequestWithAuthorityRelativePath() throws Exception {
+        final HttpRequest request = new BasicHttpRequest(Method.GET, new HttpHost("http", "somehost", -1), "stuff");
+        final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request);
+        Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod());
+        Assertions.assertEquals("stuff", httpRequestWrapper.getPath());
+        Assertions.assertEquals("stuff", httpRequestWrapper.getRequestUri());
+        Assertions.assertEquals(new URIAuthority("somehost"), httpRequestWrapper.getAuthority());
+        Assertions.assertEquals(new URI("http://somehost/stuff"), httpRequestWrapper.getUri());
+    }
+
+    @Test
+    public void testRequestHostWithReservedChars() throws Exception {
+        final HttpRequest request = new BasicHttpRequest(Method.GET, URI.create("http://someuser%21@%21example%21.com/stuff"));
+        final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request);
+        Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod());
+        Assertions.assertEquals("/stuff", httpRequestWrapper.getPath());
+        Assertions.assertEquals(new URIAuthority("someuser%21", "%21example%21.com", -1), httpRequestWrapper.getAuthority());
+        Assertions.assertEquals(new URI("http://%21example%21.com/stuff"), httpRequestWrapper.getUri());
+    }
+}
\ No newline at end of file
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpResponseWrapperTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpResponseWrapperTest.java
new file mode 100644
index 0000000..8630b73
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpResponseWrapperTest.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.message;
+
+import java.util.Locale;
+
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class HttpResponseWrapperTest {
+
+    @Test
+    void testDefaultResponseConstructors() {
+        final HttpResponse response1 = new BasicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request");
+        final HttpResponseWrapper httpResponseWrapper1 = new HttpResponseWrapper(response1);
+
+        Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, httpResponseWrapper1.getCode());
+
+        final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "whatever");
+        final HttpResponseWrapper httpResponseWrapper2 = new HttpResponseWrapper(response2);
+        Assertions.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, httpResponseWrapper2.getCode());
+        Assertions.assertEquals("whatever", httpResponseWrapper2.getReasonPhrase());
+
+        httpResponseWrapper2.setReasonPhrase("another-whatever");
+        Assertions.assertEquals("another-whatever", httpResponseWrapper2.getReasonPhrase());
+    }
+
+    @Test
+    void testSetResponseStatus() {
+        final HttpResponse response1 = new BasicHttpResponse(200, "OK");
+        final HttpResponseWrapper httpResponseWrapper1 = new HttpResponseWrapper(response1);
+
+        Assertions.assertNotNull(httpResponseWrapper1.getCode());
+        Assertions.assertEquals(200, httpResponseWrapper1.getCode());
+
+        final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request");
+        final HttpResponseWrapper httpResponseWrapper2 = new HttpResponseWrapper(response2);
+        Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, httpResponseWrapper2.getCode());
+
+        final HttpResponse response3 = new BasicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "whatever");
+        final HttpResponseWrapper httpResponseWrapper3 = new HttpResponseWrapper(response3);
+        Assertions.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, httpResponseWrapper3.getCode());
+        Assertions.assertEquals("whatever", httpResponseWrapper3.getReasonPhrase());
+
+        final HttpResponse response4 = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
+        final HttpResponseWrapper httpResponseWrapper4 = new HttpResponseWrapper(response4);
+        Assertions.assertThrows(IllegalArgumentException.class, () -> httpResponseWrapper4.setCode(-23));
+    }
+
+    @Test
+    void testLocale() {
+        final HttpResponse response = new BasicHttpResponse(200, "OK");
+        final HttpResponseWrapper httpResponseWrapper = new HttpResponseWrapper(response);
+        httpResponseWrapper.setLocale(Locale.US);
+        Assertions.assertEquals("US", httpResponseWrapper.getLocale().getCountry());
+    }
+
+}
\ No newline at end of file
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TestTlsCiphers.java b/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TestTlsCiphers.java
index 234b831..eeec5d7 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TestTlsCiphers.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TestTlsCiphers.java
@@ -74,4 +74,58 @@ public class TestTlsCiphers {
         }
     }
 
+   @Test
+    void excludeH2Blacklisted (){
+       final String[] mixCipherSuites = {
+               "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
+               "TLS_RSA_WITH_AES_256_CBC_SHA256",
+               "AES_SHA_US",
+               "TLS_RSA_WITH_AES_128_CBC_SHA",
+               "NULL_SHA",
+               "TLS_RSA_WITH_AES_256_GCM_SHA384"
+       };
+
+       final String[] strongCipherSuites = TlsCiphers.excludeH2Blacklisted(mixCipherSuites);
+       for (final String cipherSuite : strongCipherSuites) {
+           Assertions.assertFalse(TlsCiphers.isWeak(cipherSuite));
+       }
+   }
+
+    @Test
+    void excludeWeak (){
+        final String[] weakCiphersSuites = {
+                "SSL_RSA_WITH_RC4_128_SHA",
+                "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
+                "TLS_DH_anon_WITH_AES_128_CBC_SHA",
+                "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
+                "SSL_RSA_WITH_NULL_SHA",
+                "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
+                "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
+                "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
+                "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
+                "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
+                "TLS_RSA_WITH_NULL_SHA256",
+                "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
+                "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
+                "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
+                "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5",
+                "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
+                "TLS_RSA_WITH_AES_256_CBC_SHA256",
+                "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
+                "TLS_RSA_WITH_AES_128_CBC_SHA",
+                "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
+                "TLS_RSA_WITH_AES_256_GCM_SHA384"
+        };
+
+        final String[] strongCipherSuites = TlsCiphers.excludeWeak(weakCiphersSuites);
+        for (final String cipherSuite : strongCipherSuites) {
+            Assertions.assertFalse(TlsCiphers.isWeak(cipherSuite));
+        }
+    }
+
+    @Test
+    void excludeWeakNull(){
+        Assertions.assertNull(TlsCiphers.excludeWeak(null));
+    }
+
 }
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java
index d9a6cee..2677b31 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java
@@ -319,6 +319,8 @@ public class TestLaxConnPool {
         Assertions.assertEquals(0, stats.getAvailable());
         Assertions.assertEquals(0, stats.getLeased());
         Assertions.assertEquals(0, stats.getPending());
+
+        Assertions.assertFalse( pool.isShutdown());
     }
 
     @Test
@@ -390,4 +392,23 @@ public class TestLaxConnPool {
         pool.release(new PoolEntry<>("somehost"), true);
     }
 
+    @Test
+    public void testClose() {
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
+        pool.setMaxPerRoute("someRoute", 2);
+        pool.close();
+        Assertions.assertThrows(IllegalStateException.class, () -> pool.lease("someHost", null));
+        // Ignored if shut down
+        pool.release(new PoolEntry<>("someHost"), true);
+
+    }
+
+    @Test
+    public void testGetMaxPerRoute() {
+        final String route  = "someRoute";
+        final int max = 2;
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
+        pool.setMaxPerRoute(route, max);
+        Assertions.assertEquals(max, pool.getMaxPerRoute(route));
+    }
 }
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/ssl/SSLContextsTest.java b/httpcore5/src/test/java/org/apache/hc/core5/ssl/SSLContextsTest.java
new file mode 100644
index 0000000..49e0867
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/ssl/SSLContextsTest.java
@@ -0,0 +1,88 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.ssl;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.junit.jupiter.api.Test;
+
+public class SSLContextsTest {
+
+    @Test
+    void createDefault() {
+        final SSLContext sslContext = SSLContexts.createDefault();
+        assertAll(
+                () -> assertNotNull(sslContext),
+                () -> assertEquals(SSLContextBuilder.TLS, sslContext.getProtocol()),
+                () -> assertNotNull(sslContext.getProvider())
+        );
+    }
+
+    @Test
+    void createSystemDefault() {
+        final SSLContext sslContext = SSLContexts.createSystemDefault();
+        assertAll(
+                () -> assertNotNull(sslContext),
+                () -> assertEquals("Default", sslContext.getProtocol()),
+                () -> assertNotNull(sslContext.getProvider())
+        );
+    }
+
+    @Test
+    void custom() throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException {
+
+        final SSLContext sslContext = SSLContexts.custom()
+                .setKeyStoreType(KeyStore.getDefaultType())
+                .setKeyManagerFactoryAlgorithm(KeyManagerFactory.getDefaultAlgorithm())
+                .setTrustManagerFactoryAlgorithm(TrustManagerFactory.getDefaultAlgorithm())
+                .setProvider("SunJSSE")
+                .setProtocol("TLS")
+                .setSecureRandom(null)
+                .loadTrustMaterial((KeyStore) null, null)
+                .loadKeyMaterial((KeyStore) null, null, null)
+                .build();
+
+        assertAll(
+                () -> assertNotNull(sslContext),
+                () -> assertEquals(SSLContextBuilder.TLS, sslContext.getProtocol()),
+                () -> assertEquals("SunJSSE", sslContext.getProvider().getName())
+        );
+    }
+}
\ No newline at end of file

[httpcomponents-core] 03/03: HTTPCLIENT-2201: protocol exception thrown while consuming pushed headers can leave the pushed stream on the client side in an inconsistent state

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 3ca313fd114edc21b1e8d63da0ad4ca63179ce31
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Wed Feb 9 19:46:42 2022 +0100

    HTTPCLIENT-2201: protocol exception thrown while consuming pushed headers can leave the pushed stream on the client side in an inconsistent state
---
 .../http2/impl/nio/ClientPushH2StreamHandler.java  |  9 ++-
 .../core5/http2/impl/nio/NoopAsyncPushHandler.java | 72 ++++++++++++++++++++++
 2 files changed, 76 insertions(+), 5 deletions(-)

diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java
index 46c3e8c..1c9c1ae 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java
@@ -103,17 +103,16 @@ class ClientPushH2StreamHandler implements H2StreamHandler {
         if (requestState == MessageState.HEADERS) {
 
             request = DefaultH2RequestConverter.INSTANCE.convert(headers);
-
-            final AsyncPushConsumer handler;
             try {
-                handler = pushHandlerFactory != null ? pushHandlerFactory.create(request, context) : null;
+                exchangeHandler = pushHandlerFactory != null ? pushHandlerFactory.create(request, context) : null;
             } catch (final ProtocolException ex) {
+                exchangeHandler = new NoopAsyncPushHandler();
                 throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, ex.getMessage());
             }
-            if (handler == null) {
+            if (exchangeHandler == null) {
+                exchangeHandler = new NoopAsyncPushHandler();
                 throw new H2StreamResetException(H2Error.REFUSED_STREAM, "Stream refused");
             }
-            exchangeHandler = handler;
 
             context.setProtocolVersion(HttpVersion.HTTP_2);
             context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/NoopAsyncPushHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/NoopAsyncPushHandler.java
new file mode 100644
index 0000000..dc0d80b
--- /dev/null
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/NoopAsyncPushHandler.java
@@ -0,0 +1,72 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists 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.http2.impl.nio;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.nio.AsyncPushConsumer;
+import org.apache.hc.core5.http.nio.CapacityChannel;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+class NoopAsyncPushHandler implements AsyncPushConsumer {
+
+    @Override
+    public void consumePromise(final HttpRequest promise,
+                               final HttpResponse response,
+                               final EntityDetails entityDetails,
+                               final HttpContext context) throws HttpException, IOException {
+    }
+
+    @Override
+    public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
+        capacityChannel.update(Integer.MAX_VALUE);
+    }
+
+    @Override
+    public void consume(final ByteBuffer src) throws IOException {
+    }
+
+    @Override
+    public void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
+    }
+
+    @Override
+    public void failed(final Exception cause) {
+    }
+
+    @Override
+    public void releaseResources() {
+    }
+
+}