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 2024/10/20 13:31:31 UTC

(httpcomponents-core) 02/02: Redesign of compatibility integration tests for greater flexibility and extensibility; added classic i/o and TLS compatibility tests

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 1ed6e17937b80b26607af0ec8620c55c8e9285df
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Tue Oct 15 16:34:17 2024 +0200

    Redesign of compatibility integration tests for greater flexibility and extensibility; added classic i/o and TLS compatibility tests
---
 .gitignore                                         |   3 +
 .../testing/compatibility/ApacheHttpDCompatIT.java | 128 ++++++++
 .../testing/compatibility/ContainerImages.java     | 109 +++++--
 ...acheHttpDCompatIT.java => HttpBinCompatIT.java} |  43 ++-
 .../core5/testing/compatibility/NginxCompatIT.java | 128 ++++++++
 .../NginxCompatIT.java => TLSTestContexts.java}    |  55 ++--
 .../classic/ClassicHttpCompatTest.java             | 154 +++++++++
 .../classic/HttpBinClassicCompatTest.java          | 121 +++++++
 .../compatibility/nio/AsyncHttp1CompatTest.java    |  70 ++++
 .../AsyncHttp2CompatTest.java}                     | 115 ++-----
 .../AsyncHttpCompatTest.java}                      | 121 ++-----
 .../HttpBinAsyncCompatTest.java}                   |  71 ++--
 .../src/test/resources/docker/BUILDING.txt         |  43 +++
 .../{httpd-vhosts.conf => httpd-default.conf}      |  20 +-
 .../{ngnix/default.conf => httpd/httpd-h2c.conf}   |  32 +-
 .../src/test/resources/docker/httpd/httpd-ssl.conf |  53 +++
 .../resources/docker/{ngnix => nginx}/default.conf |   8 +-
 .../docker/{ngnix/default.conf => nginx/h2c.conf}  |   2 +-
 .../docker/{ngnix/default.conf => nginx/ssl.conf}  |   6 +-
 .../src/test/resources/docker/server-cert.pem      |  83 +++++
 .../src/test/resources/docker/server-key.pem       |  28 ++
 httpcore5-testing/src/test/resources/test-ca.jks   | Bin 0 -> 1037 bytes
 test-CA/ca-cert.pem                                |  23 ++
 test-CA/ca-key.pem                                 |  30 ++
 test-CA/openssl.cnf                                | 357 +++++++++++++++++++++
 25 files changed, 1450 insertions(+), 353 deletions(-)

diff --git a/.gitignore b/.gitignore
index cde3cfff4..e6786cd55 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,6 @@ maven-eclipse.xml
 *.iml
 **/log4j2-debug.xml
 **/.checkstyle
+/test-CA/newcerts/
+/test-CA/serial.txt*
+/test-CA/index.txt*
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/ApacheHttpDCompatIT.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/ApacheHttpDCompatIT.java
new file mode 100644
index 000000000..78cbd98f7
--- /dev/null
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/ApacheHttpDCompatIT.java
@@ -0,0 +1,128 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.testing.compatibility;
+
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.testing.compatibility.classic.ClassicHttpCompatTest;
+import org.apache.hc.core5.testing.compatibility.nio.AsyncHttp1CompatTest;
+import org.apache.hc.core5.testing.compatibility.nio.AsyncHttp2CompatTest;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+@Testcontainers(disabledWithoutDocker = true)
+class ApacheHttpDCompatIT {
+
+    private static Network NETWORK = Network.newNetwork();
+    @Container
+    static final GenericContainer<?> HTTPD_CONTAINER = ContainerImages.apacheHttpD(NETWORK);
+
+    @AfterAll
+    static void cleanup() {
+        HTTPD_CONTAINER.close();
+    }
+
+    static HttpHost targetContainerHost() {
+        return new HttpHost(URIScheme.HTTP.id, HTTPD_CONTAINER.getHost(), HTTPD_CONTAINER.getMappedPort(ContainerImages.HTTP_PORT));
+    }
+
+    static HttpHost targetContainerH2CHost() {
+        return new HttpHost(URIScheme.HTTP.id, HTTPD_CONTAINER.getHost(), HTTPD_CONTAINER.getMappedPort(ContainerImages.H2C_PORT));
+    }
+
+    static HttpHost targetContainerTLSHost() {
+        return new HttpHost(URIScheme.HTTPS.id, HTTPD_CONTAINER.getHost(), HTTPD_CONTAINER.getMappedPort(ContainerImages.HTTPS_PORT));
+    }
+
+    @Nested
+    @DisplayName("Classic, HTTP/1, plain")
+    class ClassicHttp1 extends ClassicHttpCompatTest {
+
+        public ClassicHttp1() throws Exception {
+            super(targetContainerHost());
+        }
+
+    }
+
+    @Nested
+    @DisplayName("Classic, HTTP/1, TLS")
+    class ClassicHttp1Tls extends ClassicHttpCompatTest {
+
+        public ClassicHttp1Tls() throws Exception {
+            super(targetContainerTLSHost());
+        }
+
+    }
+
+    @Nested
+    @DisplayName("Async, HTTP/1, plain")
+    class AsyncHttp1 extends AsyncHttp1CompatTest {
+
+        public AsyncHttp1() throws Exception {
+            super(targetContainerHost());
+        }
+
+    }
+
+    @Nested
+    @DisplayName("Async, HTTP/2, plain")
+    class AsyncHttp2 extends AsyncHttp2CompatTest {
+
+        public AsyncHttp2() throws Exception {
+            super(targetContainerH2CHost(), HttpVersionPolicy.FORCE_HTTP_2);
+        }
+
+    }
+
+    @Nested
+    @DisplayName("Async, HTTP/1, TLS")
+    class AsyncHttp1Tls extends AsyncHttp1CompatTest {
+
+        public AsyncHttp1Tls() throws Exception {
+            super(targetContainerTLSHost());
+        }
+
+    }
+
+    @Nested
+    @DisplayName("Async, protocol HTTP/2, TLS")
+    class AsyncHttp2Tls extends AsyncHttp2CompatTest {
+
+        public AsyncHttp2Tls() throws Exception {
+            super(targetContainerTLSHost(), HttpVersionPolicy.FORCE_HTTP_2);
+        }
+
+    }
+
+}
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/ContainerImages.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/ContainerImages.java
index 89d467844..ac7849a78 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/ContainerImages.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/ContainerImages.java
@@ -26,69 +26,110 @@
  */
 package org.apache.hc.core5.testing.compatibility;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
 import org.testcontainers.images.builder.ImageFromDockerfile;
 import org.testcontainers.utility.DockerImageName;
 
 public final class ContainerImages {
 
+    private static final Logger LOG = LoggerFactory.getLogger(ContainerImages.class);
+
     public static final String AAA = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
     public static final String BBB = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
     public static final String CCC = "ccccccccccccccccccccccccccccccccccccccccccccccccc";
     public static final String PUSHY = "I am being very pushy";
 
+    public static final String APACHE_HTTPD = "test-apache";
+    public static final String NGINX = "test-nginx";
+    public static final String HTTPBIN = "test-httpbin";
+
     public static int HTTP_PORT = 80;
+    public static int H2C_PORT = 81;
+    public static int HTTPS_PORT = 443;
 
-    public static GenericContainer<?> httpBin() {
+    public static GenericContainer<?> httpBin(final Network network) {
         return new GenericContainer<>(DockerImageName.parse("kennethreitz/httpbin:latest"))
+                .withNetwork(network)
+                .withNetworkAliases(HTTPBIN)
+                .withLogConsumer(new Slf4jLogConsumer(LOG))
                 .withExposedPorts(HTTP_PORT);
     }
 
-    public static GenericContainer<?> apacheHttpD() {
+    public static GenericContainer<?> apacheHttpD(final Network network) {
         return new GenericContainer<>(new ImageFromDockerfile()
-                .withFileFromClasspath("httpd-vhosts.conf", "docker/httpd/httpd-vhosts.conf")
+                .withFileFromClasspath("httpd-default.conf", "docker/httpd/httpd-default.conf")
+                .withFileFromClasspath("httpd-h2c.conf", "docker/httpd/httpd-h2c.conf")
+                .withFileFromClasspath("httpd-ssl.conf", "docker/httpd/httpd-ssl.conf")
+                .withFileFromClasspath("server-cert.pem", "docker/server-cert.pem")
+                .withFileFromClasspath("server-key.pem", "docker/server-key.pem")
                 .withFileFromString("pushy", PUSHY)
                 .withFileFromString("aaa", AAA)
                 .withFileFromString("bbb", BBB)
                 .withFileFromString("ccc", CCC)
-                .withDockerfileFromBuilder(builder ->
-                        builder
-                                .from("httpd:2.4")
-                                .env("var_dir", "/var/httpd")
-                                .env("www_dir", "${var_dir}/www")
-                                .run("mkdir -p ${var_dir}")
-                                .run("sed -i -E 's/^\\s*#\\s*(LoadModule\\s+http2_module\\s+modules\\/mod_http2.so)/\\1/' conf/httpd.conf")
-                                .run("sed -i -E 's/^\\s*ServerAdmin.*$/ServerAdmin dev@hc.apache.org/' conf/httpd.conf")
-                                .run("sed -i -E 's/^\\s*#\\s*(Include\\s+conf\\/extra\\/httpd-vhosts.conf)/\\1/' conf/httpd.conf")
-                                .copy("httpd-vhosts.conf", "/usr/local/apache2/conf/extra/httpd-vhosts.conf")
-                                .copy("pushy", "${www_dir}/")
-                                .copy("aaa", "${www_dir}/")
-                                .copy("bbb", "${www_dir}/")
-                                .copy("ccc", "${www_dir}/")
-                                .build()))
-                .withExposedPorts(HTTP_PORT);
+                .withDockerfileFromBuilder(builder -> builder
+                        .from("httpd:2.4")
+                        .env("var_dir", "/var/httpd")
+                        .env("www_dir", "${var_dir}/www")
+                        .run("mkdir -p ${var_dir}")
+                        .run("mkdir -p ${www_dir}")
+                        .run("echo '\\n" +
+                                "LoadModule http2_module modules/mod_http2.so\\n" +
+                                "LoadModule ssl_module modules/mod_ssl.so\\n" +
+                                "Include conf/extra/httpd-default.conf\\n" +
+                                "Include conf/extra/httpd-h2c.conf\\n" +
+                                "Include conf/extra/httpd-ssl.conf\\n" +
+                                "'" +
+                                " >> /usr/local/apache2/conf/httpd.conf")
+                        .copy("httpd-default.conf", "/usr/local/apache2/conf/extra/httpd-default.conf")
+                        .copy("httpd-h2c.conf", "/usr/local/apache2/conf/extra/httpd-h2c.conf")
+                        .copy("httpd-ssl.conf", "/usr/local/apache2/conf/extra/httpd-ssl.conf")
+                        .copy("server-cert.pem", "/usr/local/apache2/conf/server-cert.pem")
+                        .copy("server-key.pem", "/usr/local/apache2/conf/server-key.pem")
+                        .copy("pushy", "${www_dir}/")
+                        .copy("aaa", "${www_dir}/")
+                        .copy("bbb", "${www_dir}/")
+                        .copy("ccc", "${www_dir}/")
+                        .build()))
+                .withNetwork(network)
+                .withNetworkAliases(APACHE_HTTPD)
+                .withLogConsumer(new Slf4jLogConsumer(LOG))
+                .withExposedPorts(HTTP_PORT, H2C_PORT, HTTPS_PORT);
     }
 
-    public static GenericContainer<?> ngnix() {
+    public static GenericContainer<?> nginx(final Network network) {
         return new GenericContainer<>(new ImageFromDockerfile()
-                .withFileFromClasspath("default.conf", "docker/ngnix/default.conf")
+                .withFileFromClasspath("default.conf", "docker/nginx/default.conf")
+                .withFileFromClasspath("h2c.conf", "docker/nginx/h2c.conf")
+                .withFileFromClasspath("ssl.conf", "docker/nginx/ssl.conf")
+                .withFileFromClasspath("server-cert.pem", "docker/server-cert.pem")
+                .withFileFromClasspath("server-key.pem", "docker/server-key.pem")
                 .withFileFromString("pushy", PUSHY)
                 .withFileFromString("aaa", AAA)
                 .withFileFromString("bbb", BBB)
                 .withFileFromString("ccc", CCC)
-                .withDockerfileFromBuilder(builder ->
-                        builder
-                                .from("nginx:1.22")
-                                .env("var_dir", "/var/nginx")
-                                .env("www_dir", "${var_dir}/www")
-                                .run("mkdir -p ${var_dir}")
-                                .copy("default.conf", "/etc/nginx/conf.d/default.conf")
-                                .copy("pushy", "${www_dir}/")
-                                .copy("aaa", "${www_dir}/")
-                                .copy("bbb", "${www_dir}/")
-                                .copy("ccc", "${www_dir}/")
-                                .build()))
-                .withExposedPorts(HTTP_PORT);
+                .withDockerfileFromBuilder(builder -> builder
+                        .from("nginx:1.22")
+                        .env("var_dir", "/var/nginx")
+                        .env("www_dir", "${var_dir}/www")
+                        .run("mkdir -p ${var_dir}")
+                        .copy("default.conf", "/etc/nginx/conf.d/default.conf")
+                        .copy("h2c.conf", "/etc/nginx/conf.d/h2c.conf")
+                        .copy("ssl.conf", "/etc/nginx/conf.d/ssl.conf")
+                        .copy("server-cert.pem", "/etc/nginx/server-cert.pem")
+                        .copy("server-key.pem", "/etc/nginx/server-key.pem")
+                        .copy("pushy", "${www_dir}/")
+                        .copy("aaa", "${www_dir}/")
+                        .copy("bbb", "${www_dir}/")
+                        .copy("ccc", "${www_dir}/")
+                        .build()))
+                .withNetwork(network)
+                .withNetworkAliases(NGINX)
+                .withLogConsumer(new Slf4jLogConsumer(LOG))
+                .withExposedPorts(HTTP_PORT, H2C_PORT, HTTPS_PORT);
     }
 
 }
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/ApacheHttpDCompatIT.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/HttpBinCompatIT.java
similarity index 57%
rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/ApacheHttpDCompatIT.java
rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/HttpBinCompatIT.java
index 0277dba0a..6157acf12 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/ApacheHttpDCompatIT.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/HttpBinCompatIT.java
@@ -25,35 +25,48 @@
  *
  */
 
-package org.apache.hc.core5.testing.compatibility.http2;
+package org.apache.hc.core5.testing.compatibility;
 
 import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
-import org.apache.hc.core5.testing.compatibility.ContainerImages;
-import org.junit.jupiter.api.AfterAll;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.testing.compatibility.classic.HttpBinClassicCompatTest;
+import org.apache.hc.core5.testing.compatibility.nio.HttpBinAsyncCompatTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
 import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
 import org.testcontainers.junit.jupiter.Container;
 import org.testcontainers.junit.jupiter.Testcontainers;
 
 @Testcontainers(disabledWithoutDocker = true)
-class ApacheHttpDCompatIT extends AbstractHttp2CompatIT {
+class HttpBinCompatIT {
 
+    private static Network NETWORK = Network.newNetwork();
     @Container
-    static final GenericContainer<?> CONTAINER = ContainerImages.apacheHttpD();
+    static final GenericContainer<?> HTTP_BIN_CONTAINER = ContainerImages.httpBin(NETWORK);
 
-    @Override
-    void configure(final H2RequesterBootstrap bootstrap) {
+    static HttpHost targetContainerHost() {
+        return new HttpHost(URIScheme.HTTP.id, HTTP_BIN_CONTAINER.getHost(), HTTP_BIN_CONTAINER.getMappedPort(ContainerImages.HTTP_PORT));
     }
 
-    HttpHost targetHost() {
-        return new HttpHost("http",
-                CONTAINER.getHost(),
-                CONTAINER.getMappedPort(ContainerImages.HTTP_PORT));
+    @Nested
+    @DisplayName("Classic")
+    class Classic extends HttpBinClassicCompatTest {
+
+        public Classic() throws Exception {
+            super(targetContainerHost());
+        }
+
     }
 
-    @AfterAll
-    static void cleanup() {
-        CONTAINER.close();
+    @Nested
+    @DisplayName("Async")
+    class Async extends HttpBinAsyncCompatTest {
+
+        public Async() throws Exception {
+            super(targetContainerHost());
+        }
+
     }
 
 }
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/NginxCompatIT.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/NginxCompatIT.java
new file mode 100644
index 000000000..3a8a632b1
--- /dev/null
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/NginxCompatIT.java
@@ -0,0 +1,128 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.testing.compatibility;
+
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.testing.compatibility.classic.ClassicHttpCompatTest;
+import org.apache.hc.core5.testing.compatibility.nio.AsyncHttp1CompatTest;
+import org.apache.hc.core5.testing.compatibility.nio.AsyncHttp2CompatTest;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+@Testcontainers(disabledWithoutDocker = true)
+class NginxCompatIT {
+
+    private static Network NETWORK = Network.newNetwork();
+    @Container
+    static final GenericContainer<?> NGINX_CONTAINER = ContainerImages.nginx(NETWORK);
+
+    @AfterAll
+    static void cleanup() {
+        NGINX_CONTAINER.close();
+    }
+
+    static HttpHost targetContainerHost() {
+        return new HttpHost(URIScheme.HTTP.id, NGINX_CONTAINER.getHost(), NGINX_CONTAINER.getMappedPort(ContainerImages.HTTP_PORT));
+    }
+
+    static HttpHost targetContainerH2CHost() {
+        return new HttpHost(URIScheme.HTTP.id, NGINX_CONTAINER.getHost(), NGINX_CONTAINER.getMappedPort(ContainerImages.H2C_PORT));
+    }
+
+    static HttpHost targetContainerTLSHost() {
+        return new HttpHost(URIScheme.HTTPS.id, NGINX_CONTAINER.getHost(), NGINX_CONTAINER.getMappedPort(ContainerImages.HTTPS_PORT));
+    }
+
+    @Nested
+    @DisplayName("Classic, HTTP/1, plain")
+    class ClassicHttp1 extends ClassicHttpCompatTest {
+
+        public ClassicHttp1() throws Exception {
+            super(targetContainerHost());
+        }
+
+    }
+
+    @Nested
+    @DisplayName("Classic, HTTP/1, TLS")
+    class ClassicHttp1Tls extends ClassicHttpCompatTest {
+
+        public ClassicHttp1Tls() throws Exception {
+            super(targetContainerTLSHost());
+        }
+
+    }
+
+    @Nested
+    @DisplayName("Async, HTTP/1, plain")
+    class AsyncHttp1 extends AsyncHttp1CompatTest {
+
+        public AsyncHttp1() throws Exception {
+            super(targetContainerHost());
+        }
+
+    }
+
+    @Nested
+    @DisplayName("Async, HTTP/2, plain")
+    class AsyncHttp2 extends AsyncHttp2CompatTest {
+
+        public AsyncHttp2() throws Exception {
+            super(targetContainerH2CHost(), HttpVersionPolicy.FORCE_HTTP_2);
+        }
+
+    }
+
+    @Nested
+    @DisplayName("Async, HTTP/1, TLS")
+    class AsyncHttp1Tls extends AsyncHttp1CompatTest {
+
+        public AsyncHttp1Tls() throws Exception {
+            super(targetContainerTLSHost());
+        }
+
+    }
+
+    @Nested
+    @DisplayName("Async, HTTP/2, TLS")
+    class AsyncHttp2Tls extends AsyncHttp2CompatTest {
+
+        public AsyncHttp2Tls() throws Exception {
+            super(targetContainerTLSHost(), HttpVersionPolicy.FORCE_HTTP_2);
+        }
+
+    }
+
+}
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/NginxCompatIT.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/TLSTestContexts.java
similarity index 55%
rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/NginxCompatIT.java
rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/TLSTestContexts.java
index 06ef115bc..527f90aa7 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/NginxCompatIT.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/TLSTestContexts.java
@@ -25,35 +25,32 @@
  *
  */
 
-package org.apache.hc.core5.testing.compatibility.http2;
-
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
-import org.apache.hc.core5.testing.compatibility.ContainerImages;
-import org.junit.jupiter.api.AfterAll;
-import org.testcontainers.containers.GenericContainer;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
-
-@Testcontainers(disabledWithoutDocker = true)
-class NginxCompatIT extends AbstractHttp2CompatIT {
-
-    @Container
-    static final GenericContainer<?> CONTAINER = ContainerImages.ngnix();
-
-    @Override
-    void configure(final H2RequesterBootstrap bootstrap) {
-    }
-
-    HttpHost targetHost() {
-        return new HttpHost("http",
-                CONTAINER.getHost(),
-                CONTAINER.getMappedPort(ContainerImages.HTTP_PORT));
-    }
-
-    @AfterAll
-    static void cleanup() {
-        CONTAINER.close();
+package org.apache.hc.core5.testing.compatibility;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+
+import javax.net.ssl.SSLContext;
+
+import org.apache.hc.core5.ssl.SSLContextBuilder;
+
+public final class TLSTestContexts {
+
+    public static SSLContext createClientSSLContext() {
+        final URL keyStoreURL = TLSTestContexts.class.getResource("/test-ca.jks");
+        final String storePassword = "nopassword";
+        try {
+            return SSLContextBuilder.create()
+                    .loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
+                    .build();
+        } catch (final NoSuchAlgorithmException | KeyManagementException | KeyStoreException | CertificateException |
+                       IOException ex) {
+            throw new IllegalStateException(ex);
+        }
     }
 
 }
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/ClassicHttpCompatTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/ClassicHttpCompatTest.java
new file mode 100644
index 000000000..3b8cd1bbc
--- /dev/null
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/ClassicHttpCompatTest.java
@@ -0,0 +1,154 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.testing.compatibility.classic;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.impl.bootstrap.HttpRequester;
+import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap;
+import org.apache.hc.core5.http.io.SocketConfig;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.testing.compatibility.ContainerImages;
+import org.apache.hc.core5.testing.compatibility.Result;
+import org.apache.hc.core5.testing.compatibility.TLSTestContexts;
+import org.apache.hc.core5.testing.extension.classic.HttpRequesterResource;
+import org.apache.hc.core5.util.Timeout;
+import org.hamcrest.CoreMatchers;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public abstract class ClassicHttpCompatTest {
+
+    static final Timeout TIMEOUT = Timeout.ofSeconds(5);
+
+    private final HttpHost target;
+    @RegisterExtension
+    private final HttpRequesterResource clientResource;
+
+    public ClassicHttpCompatTest(final HttpHost target) {
+        this.target = target;
+        this.clientResource = new HttpRequesterResource();
+        this.clientResource.configure(bootstrap -> bootstrap
+                .setSocketConfig(SocketConfig.custom()
+                        .setSoTimeout(TIMEOUT)
+                        .build())
+                .setSslContext(TLSTestContexts.createClientSSLContext())
+        );
+    }
+
+    void configure(final Consumer<RequesterBootstrap> customizer) {
+        clientResource.configure(customizer);
+    }
+
+    HttpRequester client() {
+        return clientResource.start();
+    }
+
+    @Test
+    void test_sequential_requests() throws Exception {
+        final HttpRequester requester = client();
+
+        final int n = 20;
+        for (int i = 0; i < n; i++) {
+            final HttpCoreContext context = HttpCoreContext.create();
+            final ClassicHttpRequest request = ClassicRequestBuilder.get("/aaa")
+                    .setHttpHost(target)
+                    .build();
+            requester.execute(target, request, TIMEOUT, context, response -> {
+                assertThat(response.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
+                final String body1 = EntityUtils.toString(response.getEntity());
+                assertThat(body1, CoreMatchers.equalTo(ContainerImages.AAA));
+                return null;
+            });
+        }
+    }
+
+    @Test
+    void test_multi_threaded_requests() throws Exception {
+        final HttpRequester requester = client();
+
+        final int c = 10;
+        final AtomicInteger n = new AtomicInteger(20 * c);
+        final CountDownLatch countDownLatch = new CountDownLatch(c);
+        final Queue<Result<String>> resultQueue = new ConcurrentLinkedQueue<>();
+        final ExecutorService executorService = Executors.newFixedThreadPool(c);
+        try {
+            for (int i = 0; i < c; i++) {
+                executorService.execute(() -> {
+                    try {
+                        while (n.decrementAndGet() > 0) {
+                            final HttpCoreContext context = HttpCoreContext.create();
+                            final ClassicHttpRequest request = ClassicRequestBuilder.get("/aaa")
+                                    .setHttpHost(target)
+                                    .build();
+                            try {
+                                requester.execute(target, request, TIMEOUT, context, response -> {
+                                    resultQueue.add(new Result<>(
+                                            request,
+                                            response,
+                                            EntityUtils.toString(response.getEntity())));
+                                    return null;
+                                });
+                            } catch (final Exception ex) {
+                                resultQueue.add(new Result<>(request, ex));
+                            }
+                        }
+                    } finally {
+                        countDownLatch.countDown();
+                    }
+                });
+            }
+            Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), "Request executions have not completed in time");
+            for (final Result<String> result : resultQueue) {
+                if (result.isOK()) {
+                    Assertions.assertNotNull(result.response);
+                    Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode(), "Response message returned non 200 status");
+                } else {
+                    Assertions.fail(result.exception);
+                }
+            }
+        } finally {
+            executorService.shutdownNow();
+        }
+    }
+
+}
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/HttpBinClassicCompatTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/HttpBinClassicCompatTest.java
new file mode 100644
index 000000000..bf447fd0e
--- /dev/null
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/HttpBinClassicCompatTest.java
@@ -0,0 +1,121 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.testing.compatibility.classic;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.impl.bootstrap.HttpRequester;
+import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap;
+import org.apache.hc.core5.http.io.SocketConfig;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.testing.extension.classic.HttpRequesterResource;
+import org.apache.hc.core5.util.Timeout;
+import org.hamcrest.CoreMatchers;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public abstract class HttpBinClassicCompatTest {
+
+    static final Timeout TIMEOUT = Timeout.ofSeconds(5);
+
+    private final HttpHost target;
+    @RegisterExtension
+    private final HttpRequesterResource clientResource;
+
+    public HttpBinClassicCompatTest(final HttpHost target) {
+        this.target = target;
+        this.clientResource = new HttpRequesterResource();
+        this.clientResource.configure(bootstrap -> bootstrap
+                .setSocketConfig(SocketConfig.custom()
+                        .setSoTimeout(TIMEOUT)
+                        .build())
+        );
+    }
+
+    void configure(final Consumer<RequesterBootstrap> customizer) {
+        clientResource.configure(customizer);
+    }
+
+    HttpRequester client() {
+        return clientResource.start();
+    }
+
+    @Test
+    void test_sequential_request_execution() throws Exception {
+        final HttpRequester client = client();
+        final List<ClassicHttpRequest> requestMessages = Arrays.asList(
+                ClassicRequestBuilder.get("/headers")
+                        .setHttpHost(target)
+                        .build(),
+                ClassicRequestBuilder.post("/anything")
+                        .setHttpHost(target)
+                        .setEntity(new StringEntity("some important message", ContentType.TEXT_PLAIN))
+                        .build(),
+                ClassicRequestBuilder.put("/anything")
+                        .setHttpHost(target)
+                        .setEntity(new StringEntity("some important message", ContentType.TEXT_PLAIN))
+                        .build(),
+                ClassicRequestBuilder.get("/drip")
+                        .setHttpHost(target)
+                        .build(),
+                ClassicRequestBuilder.get("/bytes/20000")
+                        .setHttpHost(target)
+                        .build(),
+                ClassicRequestBuilder.get("/delay/2")
+                        .setHttpHost(target)
+                        .build(),
+                ClassicRequestBuilder.post("/delay/2")
+                        .setHttpHost(target)
+                        .setEntity(new StringEntity("some important message", ContentType.TEXT_PLAIN))
+                        .build(),
+                ClassicRequestBuilder.put("/delay/2")
+                        .setHttpHost(target)
+                        .setEntity(new StringEntity("some important message", ContentType.TEXT_PLAIN))
+                        .build()
+        );
+
+        for (final ClassicHttpRequest request : requestMessages) {
+            final HttpCoreContext context = HttpCoreContext.create();
+            client.execute(target, request, TIMEOUT, context, response -> {
+                assertThat(response.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
+                return null;
+            });
+        }
+    }
+
+}
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttp1CompatTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttp1CompatTest.java
new file mode 100644
index 000000000..2ddd89ec4
--- /dev/null
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttp1CompatTest.java
@@ -0,0 +1,70 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.testing.compatibility.nio;
+
+import java.util.function.Consumer;
+
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.http2.impl.nio.bootstrap.H2AsyncRequester;
+import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
+import org.apache.hc.core5.reactor.IOReactorConfig;
+import org.apache.hc.core5.testing.compatibility.TLSTestContexts;
+import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource;
+import org.apache.hc.core5.util.Timeout;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public abstract class AsyncHttp1CompatTest extends AsyncHttpCompatTest<H2AsyncRequester> {
+
+    static final Timeout TIMEOUT = Timeout.ofSeconds(5);
+
+    @RegisterExtension
+    private final H2AsyncRequesterResource clientResource;
+
+    public AsyncHttp1CompatTest(final HttpHost target) {
+        super(target);
+        this.clientResource = new H2AsyncRequesterResource();
+        this.clientResource.configure(bootstrap -> bootstrap
+                .setIOReactorConfig(IOReactorConfig.custom()
+                        .setSoTimeout(TIMEOUT)
+                        .build())
+                .setTlsStrategy(new BasicClientTlsStrategy(TLSTestContexts.createClientSSLContext()))
+                .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1));
+    }
+
+    void configure(final Consumer<H2RequesterBootstrap> customizer) {
+        clientResource.configure(customizer);
+    }
+
+    @Override
+    H2AsyncRequester client() {
+        return clientResource.start();
+    }
+
+}
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/AbstractHttp2CompatIT.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttp2CompatTest.java
similarity index 57%
copy from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/AbstractHttp2CompatIT.java
copy to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttp2CompatTest.java
index a2c80620f..e1f2fa577 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/AbstractHttp2CompatIT.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttp2CompatTest.java
@@ -25,12 +25,13 @@
  *
  */
 
-package org.apache.hc.core5.testing.compatibility.http2;
+package org.apache.hc.core5.testing.compatibility.nio;
 
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
+import java.util.function.Consumer;
 
 import org.apache.hc.core5.concurrent.FutureCallback;
 import org.apache.hc.core5.http.HttpHost;
@@ -40,7 +41,6 @@ import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.Message;
 import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.RequestNotExecutedException;
-import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
 import org.apache.hc.core5.http.message.BasicHttpRequest;
 import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
@@ -49,119 +49,54 @@ import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
 import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
 import org.apache.hc.core5.http2.HttpVersionPolicy;
 import org.apache.hc.core5.http2.config.H2Config;
+import org.apache.hc.core5.http2.impl.nio.bootstrap.H2AsyncRequester;
 import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
-import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
 import org.apache.hc.core5.reactor.IOReactorConfig;
-import org.apache.hc.core5.testing.classic.LoggingConnPoolListener;
-import org.apache.hc.core5.testing.compatibility.ContainerImages;
 import org.apache.hc.core5.testing.compatibility.Result;
-import org.apache.hc.core5.testing.nio.LoggingExceptionCallback;
-import org.apache.hc.core5.testing.nio.LoggingH2StreamListener;
-import org.apache.hc.core5.testing.nio.LoggingHttp1StreamListener;
-import org.apache.hc.core5.testing.nio.LoggingIOSessionDecorator;
-import org.apache.hc.core5.testing.nio.LoggingIOSessionListener;
-import org.apache.hc.core5.testing.nio.LoggingReactorMetricsListener;
+import org.apache.hc.core5.testing.compatibility.TLSTestContexts;
+import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource;
 import org.apache.hc.core5.util.Timeout;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
 
-abstract class AbstractHttp2CompatIT {
+public abstract class AsyncHttp2CompatTest extends AsyncHttpCompatTest<H2AsyncRequester> {
 
     static final Timeout TIMEOUT = Timeout.ofSeconds(5);
 
-    abstract HttpHost targetHost();
+    private final HttpHost target;
+    @RegisterExtension
+    private final H2AsyncRequesterResource clientResource;
 
-    HttpAsyncRequester client;
-
-    abstract void configure(H2RequesterBootstrap bootstrap);
-
-    @BeforeEach
-    void start() throws Exception {
-        final H2RequesterBootstrap bootstrap = H2RequesterBootstrap.bootstrap()
+    public AsyncHttp2CompatTest(final HttpHost target, final HttpVersionPolicy versionPolicy) {
+        super(target);
+        this.target = target;
+        this.clientResource = new H2AsyncRequesterResource();
+        this.clientResource.configure(bootstrap -> bootstrap
                 .setIOReactorConfig(IOReactorConfig.custom()
                         .setSoTimeout(TIMEOUT)
                         .build())
                 .setH2Config(H2Config.custom()
                         .setPushEnabled(true)
                         .build())
-                .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
-                .setStreamListener(LoggingH2StreamListener.INSTANCE)
-                .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
-                .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
-                .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
-                .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
-                .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE);
-        configure(bootstrap);
-        client = bootstrap.create();
-        client.start();
+                .setTlsStrategy(new H2ClientTlsStrategy(TLSTestContexts.createClientSSLContext()))
+                .setVersionPolicy(versionPolicy));
     }
 
-    @AfterEach
-    void shutdown() throws Exception {
-        if (client != null) {
-            client.close(CloseMode.GRACEFUL);
-        }
+    void configure(final Consumer<H2RequesterBootstrap> customizer) {
+        clientResource.configure(customizer);
     }
 
-    @Test
-    void test_multiple_request_execution() throws Exception {
-        final HttpHost target = targetHost();
-        final Future<AsyncClientEndpoint> connectFuture = client.connect(target, TIMEOUT, HttpVersionPolicy.FORCE_HTTP_2, null);
-        final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
-        try {
-            final int n = 20;
-            final CountDownLatch countDownLatch = new CountDownLatch(n);
-            final Queue<Result<String>> resultQueue = new ConcurrentLinkedQueue<>();
-            for (int i = 0; i < n; i++) {
-                final HttpRequest httpget = new BasicHttpRequest(Method.GET, target, "/aaa");
-                endpoint.execute(
-                        new BasicRequestProducer(httpget, null),
-                        new BasicResponseConsumer<>(new StringAsyncEntityConsumer()),
-                        new FutureCallback<Message<HttpResponse, String>>() {
-
-                            @Override
-                            public void completed(final Message<HttpResponse, String> responseMessage) {
-                                resultQueue.add(new Result<>(
-                                        httpget,
-                                        responseMessage.getHead(),
-                                        responseMessage.getBody()));
-                                countDownLatch.countDown();
-                            }
-
-                            @Override
-                            public void failed(final Exception ex) {
-                                resultQueue.add(new Result<>(httpget, ex));
-                                countDownLatch.countDown();
-                            }
-
-                            @Override
-                            public void cancelled() {
-                                failed(new RequestNotExecutedException());
-                            }
-
-                        });
-            }
-            Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), "Request executions have not completed in time");
-            for (final Result<String> result : resultQueue) {
-                if (result.isOK()) {
-                    Assertions.assertNotNull(result.response);
-                    Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode(), "Response message returned non 200 status");
-                    Assertions.assertEquals(ContainerImages.AAA, result.content);
-                } else {
-                    Assertions.fail(result.exception);
-                }
-            }
-        } finally {
-            endpoint.releaseAndDiscard();
-        }
+    @Override
+    H2AsyncRequester client() {
+        return clientResource.start();
     }
 
     @Test
     void test_request_execution_with_push() throws Exception {
-        final HttpHost target = targetHost();
-        final Future<AsyncClientEndpoint> connectFuture = client.connect(target, TIMEOUT, HttpVersionPolicy.FORCE_HTTP_2, null);
+        final H2AsyncRequester client = client();
+        final Future<AsyncClientEndpoint> connectFuture = client.connect(target, TIMEOUT, null, null);
         final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
         try {
             final CountDownLatch countDownLatch = new CountDownLatch(4);
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/AbstractHttp2CompatIT.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttpCompatTest.java
similarity index 56%
rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/AbstractHttp2CompatIT.java
rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttpCompatTest.java
index a2c80620f..3107316c8 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/AbstractHttp2CompatIT.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttpCompatTest.java
@@ -25,7 +25,7 @@
  *
  */
 
-package org.apache.hc.core5.testing.compatibility.http2;
+package org.apache.hc.core5.testing.compatibility.nio;
 
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
@@ -44,71 +44,30 @@ import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
 import org.apache.hc.core5.http.message.BasicHttpRequest;
 import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
-import org.apache.hc.core5.http.nio.support.AbstractAsyncPushHandler;
 import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
 import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
-import org.apache.hc.core5.http2.HttpVersionPolicy;
-import org.apache.hc.core5.http2.config.H2Config;
-import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
-import org.apache.hc.core5.io.CloseMode;
-import org.apache.hc.core5.reactor.IOReactorConfig;
-import org.apache.hc.core5.testing.classic.LoggingConnPoolListener;
 import org.apache.hc.core5.testing.compatibility.ContainerImages;
 import org.apache.hc.core5.testing.compatibility.Result;
-import org.apache.hc.core5.testing.nio.LoggingExceptionCallback;
-import org.apache.hc.core5.testing.nio.LoggingH2StreamListener;
-import org.apache.hc.core5.testing.nio.LoggingHttp1StreamListener;
-import org.apache.hc.core5.testing.nio.LoggingIOSessionDecorator;
-import org.apache.hc.core5.testing.nio.LoggingIOSessionListener;
-import org.apache.hc.core5.testing.nio.LoggingReactorMetricsListener;
 import org.apache.hc.core5.util.Timeout;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-abstract class AbstractHttp2CompatIT {
+public abstract class AsyncHttpCompatTest<T extends HttpAsyncRequester> {
 
     static final Timeout TIMEOUT = Timeout.ofSeconds(5);
 
-    abstract HttpHost targetHost();
-
-    HttpAsyncRequester client;
-
-    abstract void configure(H2RequesterBootstrap bootstrap);
-
-    @BeforeEach
-    void start() throws Exception {
-        final H2RequesterBootstrap bootstrap = H2RequesterBootstrap.bootstrap()
-                .setIOReactorConfig(IOReactorConfig.custom()
-                        .setSoTimeout(TIMEOUT)
-                        .build())
-                .setH2Config(H2Config.custom()
-                        .setPushEnabled(true)
-                        .build())
-                .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
-                .setStreamListener(LoggingH2StreamListener.INSTANCE)
-                .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
-                .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
-                .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
-                .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
-                .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE);
-        configure(bootstrap);
-        client = bootstrap.create();
-        client.start();
-    }
+    private final HttpHost target;
 
-    @AfterEach
-    void shutdown() throws Exception {
-        if (client != null) {
-            client.close(CloseMode.GRACEFUL);
-        }
+    public AsyncHttpCompatTest(final HttpHost target) {
+        this.target = target;
     }
 
+    abstract T client();
+
     @Test
-    void test_multiple_request_execution() throws Exception {
-        final HttpHost target = targetHost();
-        final Future<AsyncClientEndpoint> connectFuture = client.connect(target, TIMEOUT, HttpVersionPolicy.FORCE_HTTP_2, null);
+    void test_multiple_request_execution_over_same_connection() throws Exception {
+        final HttpAsyncRequester client = client();
+        final Future<AsyncClientEndpoint> connectFuture = client.connect(target, TIMEOUT, null, null);
         final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
         try {
             final int n = 20;
@@ -159,37 +118,20 @@ abstract class AbstractHttp2CompatIT {
     }
 
     @Test
-    void test_request_execution_with_push() throws Exception {
-        final HttpHost target = targetHost();
-        final Future<AsyncClientEndpoint> connectFuture = client.connect(target, TIMEOUT, HttpVersionPolicy.FORCE_HTTP_2, null);
-        final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
-        try {
-            final CountDownLatch countDownLatch = new CountDownLatch(4);
-            final Queue<Result<String>> resultQueue = new ConcurrentLinkedQueue<>();
-            final HttpRequest httpget = new BasicHttpRequest(Method.GET, target, "/pushy");
-            endpoint.execute(
+    void test_multiple_request_execution_over_multiple_connections() throws Exception {
+        final HttpAsyncRequester client = client();
+        final int c = 10;
+        client.setDefaultMaxPerRoute(10);
+        final int n = 20 * c;
+        final CountDownLatch countDownLatch = new CountDownLatch(n);
+        final Queue<Result<String>> resultQueue = new ConcurrentLinkedQueue<>();
+        for (int i = 0; i < n; i++) {
+            final HttpRequest httpget = new BasicHttpRequest(Method.GET, target, "/aaa");
+            client.execute(
+                    target,
                     new BasicRequestProducer(httpget, null),
                     new BasicResponseConsumer<>(new StringAsyncEntityConsumer()),
-                    (request, context) ->
-                            new AbstractAsyncPushHandler<Message<HttpResponse, String>>(new BasicResponseConsumer<>(new StringAsyncEntityConsumer())) {
-
-                                @Override
-                                protected void handleResponse(final HttpRequest promise, final Message<HttpResponse, String> responseMessage) {
-                                    resultQueue.add(new Result<>(
-                                            httpget,
-                                            responseMessage.getHead(),
-                                            responseMessage.getBody()));
-                                    countDownLatch.countDown();
-                                }
-
-                                @Override
-                                protected void handleError(final HttpRequest promise, final Exception cause) {
-                                    resultQueue.add(new Result<>(httpget, cause));
-                                    countDownLatch.countDown();
-                                }
-
-                            },
-                    null,
+                    TIMEOUT,
                     new FutureCallback<Message<HttpResponse, String>>() {
 
                         @Override
@@ -213,17 +155,16 @@ abstract class AbstractHttp2CompatIT {
                         }
 
                     });
-            Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), "Request executions have not completed in time");
-            for (final Result<String> result : resultQueue) {
-                if (result.isOK()) {
-                    Assertions.assertNotNull(result.response);
-                    Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode(), "Response message returned non 200 status");
-                } else {
-                    Assertions.fail(result.exception);
-                }
+        }
+        Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), "Request executions have not completed in time");
+        for (final Result<String> result : resultQueue) {
+            if (result.isOK()) {
+                Assertions.assertNotNull(result.response);
+                Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode(), "Response message returned non 200 status");
+                Assertions.assertEquals(ContainerImages.AAA, result.content);
+            } else {
+                Assertions.fail(result.exception);
             }
-        } finally {
-            endpoint.releaseAndDiscard();
         }
     }
 
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http1/HttpBinCompatIT.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/HttpBinAsyncCompatTest.java
similarity index 78%
rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http1/HttpBinCompatIT.java
rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/HttpBinAsyncCompatTest.java
index 38b063d88..f0dd5f5aa 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http1/HttpBinCompatIT.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/HttpBinAsyncCompatTest.java
@@ -25,7 +25,7 @@
  *
  */
 
-package org.apache.hc.core5.testing.compatibility.http1;
+package org.apache.hc.core5.testing.compatibility.nio;
 
 import java.nio.charset.CodingErrorAction;
 import java.nio.charset.StandardCharsets;
@@ -54,76 +54,41 @@ import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
 import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
 import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
-import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
-import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.http2.impl.nio.bootstrap.H2AsyncRequester;
 import org.apache.hc.core5.reactor.IOReactorConfig;
-import org.apache.hc.core5.testing.classic.LoggingConnPoolListener;
-import org.apache.hc.core5.testing.compatibility.ContainerImages;
 import org.apache.hc.core5.testing.compatibility.Result;
-import org.apache.hc.core5.testing.nio.LoggingExceptionCallback;
-import org.apache.hc.core5.testing.nio.LoggingH2StreamListener;
-import org.apache.hc.core5.testing.nio.LoggingHttp1StreamListener;
-import org.apache.hc.core5.testing.nio.LoggingIOSessionDecorator;
-import org.apache.hc.core5.testing.nio.LoggingIOSessionListener;
-import org.apache.hc.core5.testing.nio.LoggingReactorMetricsListener;
+import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource;
 import org.apache.hc.core5.util.Timeout;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.testcontainers.containers.GenericContainer;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
+import org.junit.jupiter.api.extension.RegisterExtension;
 
-@Testcontainers(disabledWithoutDocker = true)
-class HttpBinCompatIT {
+public abstract class HttpBinAsyncCompatTest {
 
     static final Timeout TIMEOUT = Timeout.ofSeconds(5);
 
-    @Container
-    static final GenericContainer<?> CONTAINER = ContainerImages.httpBin();
+    private final HttpHost target;
+    @RegisterExtension
+    private final H2AsyncRequesterResource clientResource;
 
-    static HttpHost targetHost() {
-        return new HttpHost("http",
-                CONTAINER.getHost(),
-                CONTAINER.getMappedPort(ContainerImages.HTTP_PORT));
-    }
-
-    HttpAsyncRequester client;
-
-    @BeforeEach
-    void start() throws Exception {
-        client = H2RequesterBootstrap.bootstrap()
+    public HttpBinAsyncCompatTest(final HttpHost target) {
+        this.target = target;
+        this.clientResource = new H2AsyncRequesterResource();
+        this.clientResource.configure(bootstrap -> bootstrap
                 .setIOReactorConfig(IOReactorConfig.custom()
                         .setSoTimeout(TIMEOUT)
                         .build())
-                .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
-                .setStreamListener(LoggingH2StreamListener.INSTANCE)
-                .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
-                .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
-                .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
-                .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
-                .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE)
-                .create();
-        client.start();
-    }
-
-    @AfterEach
-    void shutdown() throws Exception {
-        if (client != null) {
-            client.close(CloseMode.GRACEFUL);
-        }
+                .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1));
     }
 
-    @AfterAll
-    static void cleanup() {
-        CONTAINER.close();
+    H2AsyncRequester client() {
+        return clientResource.start();
     }
 
     @Test
     void test_sequential_request_execution() throws Exception {
-        final HttpHost target = targetHost();
+        final HttpAsyncRequester client = client();
         final List<Message<HttpRequest, AsyncEntityProducer>> requestMessages = Arrays.asList(
                 new Message<>(new BasicHttpRequest(Method.GET, target, "/headers")),
                 new Message<>(
@@ -162,7 +127,7 @@ class HttpBinCompatIT {
 
     @Test
     void test_pipelined_request_execution() throws Exception {
-        final HttpHost target = targetHost();
+        final HttpAsyncRequester client = client();
         final Future<AsyncClientEndpoint> connectFuture = client.connect(target, TIMEOUT);
         final AsyncClientEndpoint streamEndpoint = connectFuture.get();
         try {
diff --git a/httpcore5-testing/src/test/resources/docker/BUILDING.txt b/httpcore5-testing/src/test/resources/docker/BUILDING.txt
new file mode 100644
index 000000000..8896f184f
--- /dev/null
+++ b/httpcore5-testing/src/test/resources/docker/BUILDING.txt
@@ -0,0 +1,43 @@
+= SSL key / cert material
+
+Execute in the project root
+
+# Issue a certificate request
+---
+openssl req -config test-CA/openssl.cnf -new -nodes -sha256 -days 36500  \
+ -subj '/O=Apache Software Foundation/OU=HttpComponents Project/CN=localhost/emailAddress=dev@hc.apache.org/' \
+ -addext 'subjectAltName = DNS:localhost' \
+ -keyout httpcore5-testing/src/test/resources/docker/server-key.pem \
+ -out httpcore5-testing/src/test/resources/docker/server-certreq.pem
+---
+# Verify the request
+---
+openssl req -in httpcore5-testing/src/test/resources/docker/server-certreq.pem -text -noout
+---
+# Sign new certificate with the test CA key
+---
+openssl ca -config test-CA/openssl.cnf -days 36500 \
+ -out httpcore5-testing/src/test/resources/docker/server-cert.pem \
+ -in httpcore5-testing/src/test/resources/docker/server-certreq.pem \
+ && rm httpcore5-testing/src/test/resources/docker/server-certreq.pem
+---
+
+# Create JKS store with the Test CA cert
+keytool -import -trustcacerts -alias test-ca -file test-CA/ca-cert.pem -keystore httpcore5-testing/src/test/resources/test-ca.jks -storepass nopassword
+
+= Running a local version of HttpBin
+
+# Create container
+---
+docker container create -it --name test-httpbin -p 8080:80 kennethreitz/httpbin:latest
+docker start test-httpbin
+docker logs test-httpbin
+---
+# Trouble-shoot container
+---
+docker exec -it test-httpbin bash
+---
+# Delete container
+---
+docker rm --force test-httpbin
+---
\ No newline at end of file
diff --git a/httpcore5-testing/src/test/resources/docker/httpd/httpd-vhosts.conf b/httpcore5-testing/src/test/resources/docker/httpd/httpd-default.conf
similarity index 79%
rename from httpcore5-testing/src/test/resources/docker/httpd/httpd-vhosts.conf
rename to httpcore5-testing/src/test/resources/docker/httpd/httpd-default.conf
index fa29ea9c4..8465bdf60 100644
--- a/httpcore5-testing/src/test/resources/docker/httpd/httpd-vhosts.conf
+++ b/httpcore5-testing/src/test/resources/docker/httpd/httpd-default.conf
@@ -13,10 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-ServerName localhost:80
-
-Protocols h2c http/1.1
-
 <Directory />
     AllowOverride none
     Require all denied
@@ -29,15 +25,7 @@ DocumentRoot "/var/httpd/www"
     Require all granted
 </Directory>
 
-<IfModule http2_module>
-    LogLevel http2:info
-    H2Push on
-    H2EarlyHints on
-
-    <Location /pushy>
-        H2PushResource /aaa
-        H2PushResource /bbb
-        H2PushResource /ccc
-    </Location>
-
-</IfModule>
+<VirtualHost _default_:80>
+    ServerName localhost:80
+    Protocols http/1.1
+</VirtualHost>
diff --git a/httpcore5-testing/src/test/resources/docker/ngnix/default.conf b/httpcore5-testing/src/test/resources/docker/httpd/httpd-h2c.conf
similarity index 67%
copy from httpcore5-testing/src/test/resources/docker/ngnix/default.conf
copy to httpcore5-testing/src/test/resources/docker/httpd/httpd-h2c.conf
index c48cd4248..9091a37dd 100644
--- a/httpcore5-testing/src/test/resources/docker/ngnix/default.conf
+++ b/httpcore5-testing/src/test/resources/docker/httpd/httpd-h2c.conf
@@ -13,24 +13,22 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-server {
-    listen       80 http2;
-    server_name  localhost;
+Listen 81
 
-    location / {
-        root   /var/nginx/www;
+<VirtualHost _default_:81>
+    ServerName localhost:81
+    Protocols h2c
 
-        location = /pushy {
-            http2_push      "/aaa";
-            http2_push      "/bbb";
-            http2_push      "/ccc";
-        }
-    }
+    <IfModule http2_module>
+        LogLevel http2:info
+        H2Push on
+        H2EarlyHints on
 
-    error_page   500 502 503 504  /50x.html;
-    location = /50x.html {
-        root   /usr/share/nginx/html;
-    }
-
-}
+        <Location /pushy>
+            H2PushResource /aaa
+            H2PushResource /bbb
+            H2PushResource /ccc
+        </Location>
+    </IfModule>
 
+</VirtualHost>
diff --git a/httpcore5-testing/src/test/resources/docker/httpd/httpd-ssl.conf b/httpcore5-testing/src/test/resources/docker/httpd/httpd-ssl.conf
new file mode 100644
index 000000000..d9722d24c
--- /dev/null
+++ b/httpcore5-testing/src/test/resources/docker/httpd/httpd-ssl.conf
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+Listen 443
+
+SSLCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES
+SSLProxyCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES
+
+SSLHonorCipherOrder on
+
+SSLProtocol all -SSLv3
+SSLProxyProtocol all -SSLv3
+
+SSLPassPhraseDialog  builtin
+
+SSLSessionCacheTimeout  300
+
+<VirtualHost _default_:443>
+    ServerName localhost:443
+    Protocols h2 http/1.1
+
+    SSLEngine on
+    SSLCertificateFile "/usr/local/apache2/conf/server-cert.pem"
+    SSLCertificateKeyFile "/usr/local/apache2/conf/server-key.pem"
+    CustomLog /proc/self/fd/1 \
+              "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
+
+    <IfModule http2_module>
+        LogLevel http2:info
+        H2Push on
+        H2EarlyHints on
+
+        <Location /pushy>
+            H2PushResource /aaa
+            H2PushResource /bbb
+            H2PushResource /ccc
+        </Location>
+    </IfModule>
+
+</VirtualHost>
diff --git a/httpcore5-testing/src/test/resources/docker/ngnix/default.conf b/httpcore5-testing/src/test/resources/docker/nginx/default.conf
similarity index 85%
copy from httpcore5-testing/src/test/resources/docker/ngnix/default.conf
copy to httpcore5-testing/src/test/resources/docker/nginx/default.conf
index c48cd4248..99b2ba4b7 100644
--- a/httpcore5-testing/src/test/resources/docker/ngnix/default.conf
+++ b/httpcore5-testing/src/test/resources/docker/nginx/default.conf
@@ -14,17 +14,11 @@
 # limitations under the License.
 
 server {
-    listen       80 http2;
+    listen       80;
     server_name  localhost;
 
     location / {
         root   /var/nginx/www;
-
-        location = /pushy {
-            http2_push      "/aaa";
-            http2_push      "/bbb";
-            http2_push      "/ccc";
-        }
     }
 
     error_page   500 502 503 504  /50x.html;
diff --git a/httpcore5-testing/src/test/resources/docker/ngnix/default.conf b/httpcore5-testing/src/test/resources/docker/nginx/h2c.conf
similarity index 97%
copy from httpcore5-testing/src/test/resources/docker/ngnix/default.conf
copy to httpcore5-testing/src/test/resources/docker/nginx/h2c.conf
index c48cd4248..f994f2f47 100644
--- a/httpcore5-testing/src/test/resources/docker/ngnix/default.conf
+++ b/httpcore5-testing/src/test/resources/docker/nginx/h2c.conf
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 server {
-    listen       80 http2;
+    listen       81 http2;
     server_name  localhost;
 
     location / {
diff --git a/httpcore5-testing/src/test/resources/docker/ngnix/default.conf b/httpcore5-testing/src/test/resources/docker/nginx/ssl.conf
similarity index 83%
rename from httpcore5-testing/src/test/resources/docker/ngnix/default.conf
rename to httpcore5-testing/src/test/resources/docker/nginx/ssl.conf
index c48cd4248..e0d83e10a 100644
--- a/httpcore5-testing/src/test/resources/docker/ngnix/default.conf
+++ b/httpcore5-testing/src/test/resources/docker/nginx/ssl.conf
@@ -14,8 +14,12 @@
 # limitations under the License.
 
 server {
-    listen       80 http2;
+    listen       443 ssl http2;
     server_name  localhost;
+    ssl_certificate     /etc/nginx/server-cert.pem;
+    ssl_certificate_key /etc/nginx/server-key.pem;
+    ssl_protocols       TLSv1.2 TLSv1.3;
+    ssl_ciphers         HIGH:!aNULL:!MD5;
 
     location / {
         root   /var/nginx/www;
diff --git a/httpcore5-testing/src/test/resources/docker/server-cert.pem b/httpcore5-testing/src/test/resources/docker/server-cert.pem
new file mode 100644
index 000000000..ce3990fdd
--- /dev/null
+++ b/httpcore5-testing/src/test/resources/docker/server-cert.pem
@@ -0,0 +1,83 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 0 (0x0)
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: O=Apache Software Foundation, OU=HttpComponents Project, CN=Test CA/emailAddress=dev@hc.apache.org
+        Validity
+            Not Before: Oct 20 11:31:00 2024 GMT
+            Not After : Sep 26 11:31:00 2124 GMT
+        Subject: O=Apache Software Foundation, OU=HttpComponents Project, CN=localhost/emailAddress=dev@hc.apache.org
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:9a:cf:ff:49:86:9e:3a:0b:0c:2f:4f:89:10:86:
+                    11:30:da:81:8c:9e:26:f7:72:9f:c7:d0:8e:16:67:
+                    be:0a:46:ee:d2:9f:67:89:37:b8:38:84:16:d8:66:
+                    c1:c8:9a:9f:64:c6:06:8c:57:fe:0d:83:58:4d:3d:
+                    f3:d5:35:a7:89:e3:5e:1b:c8:48:b5:66:34:38:ec:
+                    9f:ac:1a:05:81:66:0f:d4:9f:af:a9:0c:bf:20:51:
+                    e5:1c:21:63:a5:95:39:86:b0:d6:eb:9d:9a:29:be:
+                    83:df:ef:6b:08:e4:e3:2e:a3:1e:49:4c:b1:2a:f7:
+                    89:49:45:4c:57:c0:47:63:49:5f:85:86:72:d7:06:
+                    d1:e6:10:16:ab:60:a3:09:7d:9b:39:e4:13:4b:68:
+                    8e:c3:33:a0:50:ce:45:41:50:f6:38:d8:3e:89:e8:
+                    cf:cd:15:24:3f:ea:37:a5:72:37:f8:7b:60:ce:d9:
+                    6b:e1:ef:bc:92:13:48:7a:97:c8:48:87:62:4b:7c:
+                    0e:6f:bf:a5:b7:47:78:2a:5d:74:92:5c:3a:40:8a:
+                    f2:e2:d8:35:54:b2:13:1f:d7:c8:0b:c8:d9:89:56:
+                    bd:72:d0:84:14:c8:b6:79:b8:22:b7:d0:20:9a:ef:
+                    59:0d:ca:0c:99:ad:27:da:3f:c8:f5:02:3b:e2:5f:
+                    98:6d
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                F5:D3:EA:60:F9:5C:9E:DD:82:9D:DF:E4:71:43:A6:35:84:4D:07:46
+            X509v3 Authority Key Identifier: 
+                03:E4:E7:DA:0F:64:DB:13:1E:BD:85:AB:76:BC:29:CA:2F:A7:C7:4B
+    Signature Algorithm: sha256WithRSAEncryption
+    Signature Value:
+        97:67:38:35:c2:da:ab:2b:22:5f:29:0a:cc:b3:a0:3c:a0:a5:
+        5c:a3:75:ef:48:65:b5:45:3f:14:11:7b:95:09:78:e4:9a:b1:
+        db:10:23:33:7f:c8:37:89:1f:e4:be:51:48:8c:d4:1d:6b:25:
+        fd:ed:30:24:eb:20:f7:84:c4:49:d1:81:6d:f4:f5:54:71:ac:
+        a0:40:b1:c1:05:3a:fb:cb:79:5a:ee:ce:00:20:7c:31:f1:9a:
+        16:1a:11:19:79:dd:49:8a:1b:91:e9:30:4f:33:72:2e:ee:71:
+        5e:53:4a:59:9f:80:4b:cf:6a:da:c8:98:20:55:a1:45:c2:cc:
+        86:05:45:49:df:72:48:54:88:02:91:aa:1b:e3:ed:b6:29:30:
+        a7:d3:46:d6:ab:58:c2:e5:1e:e6:8d:67:c0:2b:54:46:75:97:
+        f0:5a:f8:c2:04:8f:ce:31:50:28:de:37:dc:c6:8d:6b:24:f8:
+        fa:6c:ae:d9:bd:fc:87:22:dd:cc:c9:a0:c2:20:5f:13:ca:f8:
+        32:96:66:14:1d:81:b7:36:4a:36:09:46:83:3d:80:07:9f:8b:
+        64:e4:5c:42:93:55:c1:ea:f9:65:6d:10:b5:33:ee:90:27:11:
+        3a:30:14:bf:a8:c8:ca:f0:fe:22:1b:b1:f0:76:b0:a4:32:3c:
+        4a:52:af:1c
+-----BEGIN CERTIFICATE-----
+MIID7jCCAtagAwIBAgIBADANBgkqhkiG9w0BAQsFADB6MSMwIQYDVQQKDBpBcGFj
+aGUgU29mdHdhcmUgRm91bmRhdGlvbjEfMB0GA1UECwwWSHR0cENvbXBvbmVudHMg
+UHJvamVjdDEQMA4GA1UEAwwHVGVzdCBDQTEgMB4GCSqGSIb3DQEJARYRZGV2QGhj
+LmFwYWNoZS5vcmcwIBcNMjQxMDIwMTEzMTAwWhgPMjEyNDA5MjYxMTMxMDBaMHwx
+IzAhBgNVBAoMGkFwYWNoZSBTb2Z0d2FyZSBGb3VuZGF0aW9uMR8wHQYDVQQLDBZI
+dHRwQ29tcG9uZW50cyBQcm9qZWN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxIDAeBgkq
+hkiG9w0BCQEWEWRldkBoYy5hcGFjaGUub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAms//SYaeOgsML0+JEIYRMNqBjJ4m93Kfx9COFme+Ckbu0p9n
+iTe4OIQW2GbByJqfZMYGjFf+DYNYTT3z1TWnieNeG8hItWY0OOyfrBoFgWYP1J+v
+qQy/IFHlHCFjpZU5hrDW652aKb6D3+9rCOTjLqMeSUyxKveJSUVMV8BHY0lfhYZy
+1wbR5hAWq2CjCX2bOeQTS2iOwzOgUM5FQVD2ONg+iejPzRUkP+o3pXI3+Htgztlr
+4e+8khNIepfISIdiS3wOb7+lt0d4Kl10klw6QIry4tg1VLITH9fIC8jZiVa9ctCE
+FMi2ebgit9Agmu9ZDcoMma0n2j/I9QI74l+YbQIDAQABo3sweTAJBgNVHRMEAjAA
+MCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAd
+BgNVHQ4EFgQU9dPqYPlcnt2Cnd/kcUOmNYRNB0YwHwYDVR0jBBgwFoAUA+Tn2g9k
+2xMevYWrdrwpyi+nx0swDQYJKoZIhvcNAQELBQADggEBAJdnODXC2qsrIl8pCsyz
+oDygpVyjde9IZbVFPxQRe5UJeOSasdsQIzN/yDeJH+S+UUiM1B1rJf3tMCTrIPeE
+xEnRgW309VRxrKBAscEFOvvLeVruzgAgfDHxmhYaERl53UmKG5HpME8zci7ucV5T
+SlmfgEvPatrImCBVoUXCzIYFRUnfckhUiAKRqhvj7bYpMKfTRtarWMLlHuaNZ8Ar
+VEZ1l/Ba+MIEj84xUCjeN9zGjWsk+Ppsrtm9/Ici3czJoMIgXxPK+DKWZhQdgbc2
+SjYJRoM9gAefi2TkXEKTVcHq+WVtELUz7pAnETowFL+oyMrw/iIbsfB2sKQyPEpS
+rxw=
+-----END CERTIFICATE-----
diff --git a/httpcore5-testing/src/test/resources/docker/server-key.pem b/httpcore5-testing/src/test/resources/docker/server-key.pem
new file mode 100644
index 000000000..5871ecc91
--- /dev/null
+++ b/httpcore5-testing/src/test/resources/docker/server-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCaz/9Jhp46Cwwv
+T4kQhhEw2oGMnib3cp/H0I4WZ74KRu7Sn2eJN7g4hBbYZsHImp9kxgaMV/4Ng1hN
+PfPVNaeJ414byEi1ZjQ47J+sGgWBZg/Un6+pDL8gUeUcIWOllTmGsNbrnZopvoPf
+72sI5OMuox5JTLEq94lJRUxXwEdjSV+FhnLXBtHmEBarYKMJfZs55BNLaI7DM6BQ
+zkVBUPY42D6J6M/NFSQ/6jelcjf4e2DO2Wvh77ySE0h6l8hIh2JLfA5vv6W3R3gq
+XXSSXDpAivLi2DVUshMf18gLyNmJVr1y0IQUyLZ5uCK30CCa71kNygyZrSfaP8j1
+AjviX5htAgMBAAECggEABtpp+tduk141f2CoziZJMN2eD4YEGT8l60KvD4yEdqWr
+i9UKUHeQJvxTRegimvPxVhTdaa1kLd/1yDw+WjQNAxx2vYnGYjVbNCE8HkfBR6L8
+1L+Zl0UHrvFr0CzQNvPIqfmTwgwQ14PmCENBJzx5VsavdV+OQOXYBM+AW4AZCOrp
+yrRUVklecZwOF+lrkS+QgUuv5ZvufJq20PcuGhMGwFHaiOH+BHSfcPdqLKwxUjv0
+vpN36cXw4vELYUMpM7jd4B+0gsoJZXqFwTHBt/3wrqlOFWFqq+X+k9RcmWofP1kT
+yZGOKhexw6tuQB25ejnGJEIAcnCGT2fX7WMmPCoB0QKBgQDOv8OJwKiKLYNSv1Ln
+Ep6Q71xx2P7UcE6fF08mI+U8j1zAGnUVrVS2M9ysTyuZuiLtj6dBZ63ej4hdC+Q5
+elLo/r8KFR5k+i6KT6XfidpOcIxhIAIjHaXx/X/x1jOGl6kvr4CJqdWd9wrMMglx
+Wi3EmsVHmcjB14xtKX0WgXvJSQKBgQC/sPhWHOFAraM4Izemd+0GL4LkjpdtiWyQ
+TiFjvlSbYTgPyl3d8IQY5ubWpHfx7meZmWNj6lmiSBUIFV5/TNpuPbr5wD/KK/L6
+2cZevlPUYC4nSF+PTB9Hm5c6ocLbP6ddcD98cQz2efQZXixQhECoX8UDMxL3pIjb
+LdPrTalaBQKBgDiZ+1h9LCcYKrm+s12blVmsjiyE8c8LKBsZ8ItKjHm17fiRxbg3
+ideP/zwVOKSPdvx+tZWMYudpeDWxCbxKpuQuyfJisb2HWLJ497ppLSEIYwY8Exm2
+BjZn5l04/UY9ww3RCGmn21mxxnz8veUQgTMwBA6EJZgcAM/p6dr9e/wxAoGBAIQj
+AYyaq8th5jk0TZA66vBwn9HdWRTwvh1bPjj7YmcZRQPdg4cR3H0t7cHX7bafmFqR
+GaouSUtMVPMVh+ZIm+mSM+klVNOQKBYzOELm//JNuoLpqLWmy4kY/84y45cu8be/
+QIQPidf9iP5TpEh8YfWuzqe99T0v9gbgOEzjY/HhAoGBALr7Cwu3mvNLFug3n5UW
+1o+ImxlSI5LI1eqV9gG4pfKZbWzXuw8fXd8LLBxF3+WoE6eoSnnYVce8aUX3nDhd
+ZxUPattdKDOXmItHW+Oak2jZB6OSyTPM0uZWt3KfPDE+dPwnLmKuc7ZElo0rVwYW
+UiQnz6mwRNWK1Z/OGfkXqTxw
+-----END PRIVATE KEY-----
diff --git a/httpcore5-testing/src/test/resources/test-ca.jks b/httpcore5-testing/src/test/resources/test-ca.jks
new file mode 100644
index 000000000..fb70ffb15
Binary files /dev/null and b/httpcore5-testing/src/test/resources/test-ca.jks differ
diff --git a/test-CA/ca-cert.pem b/test-CA/ca-cert.pem
new file mode 100644
index 000000000..a40c7631b
--- /dev/null
+++ b/test-CA/ca-cert.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDyTCCArGgAwIBAgIJAO3mCIu9mboMMA0GCSqGSIb3DQEBCwUAMHoxIzAhBgNV
+BAoMGkFwYWNoZSBTb2Z0d2FyZSBGb3VuZGF0aW9uMR8wHQYDVQQLDBZIdHRwQ29t
+cG9uZW50cyBQcm9qZWN0MRAwDgYDVQQDDAdUZXN0IENBMSAwHgYJKoZIhvcNAQkB
+FhFkZXZAaGMuYXBhY2hlLm9yZzAgFw0xNDEwMTMxNTAxMjBaGA8yMjg4MDcyODE1
+MDEyMFowejEjMCEGA1UECgwaQXBhY2hlIFNvZnR3YXJlIEZvdW5kYXRpb24xHzAd
+BgNVBAsMFkh0dHBDb21wb25lbnRzIFByb2plY3QxEDAOBgNVBAMMB1Rlc3QgQ0Ex
+IDAeBgkqhkiG9w0BCQEWEWRldkBoYy5hcGFjaGUub3JnMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEApXhHtKRvAxbLI+f21zNe68dkVXAhSMIfHQJGb2en
+S1H8yE4HPIb4vPQ0U7fQCb7RXplm6cHExpof4cO3DmyqD5KeQk0TdM8XrhviDgwj
+Y0KQ/lgwGHR5CpYoZ6LYWaLSE/wt9dVu80UcK8a3hW9G0X/4b79fMO6HYDix+CI4
+b17sqZ4K0tWKA10Xe+2RJU8Y01pPBaPR/UsAn+a1pZ6f8BhL879oWHfLWKcgZOYP
+U4sYED0S8gs4/ED1zRj2/uHb313sHTl+OU4X5v+OvwBvbNBrl5qfMTZnRNxlOfRq
+UTJdcopsp2aNeqHiorSDOrHwMIJpxQ2XqHT2l9s8msXf4wIDAQABo1AwTjAdBgNV
+HQ4EFgQUA+Tn2g9k2xMevYWrdrwpyi+nx0swHwYDVR0jBBgwFoAUA+Tn2g9k2xMe
+vYWrdrwpyi+nx0swDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAFVEp
+8Nv6JoFY7oGgu6068fH/kq7A3SllqMpbv7dddI9fgwy352cBtg6PkYkGtEE037xs
+FQSYV1NiAkNWTJER+Q+kVbQrhuPNKZqh1g0sUKwv3X20BmgJ9hbU9klWZjdjujyd
+h9Ybjuntkn5XPp1zN6zHD0sQReEJnRlD6FT1axrQWpICzE4qoo8k64G+6/rqFywc
+oMc/Of3KCAHjtbWklEu97hjBvGC/nEP4/VhRrjWWSeGHv88LCyO/Yg6v3zrZHFLW
++KhsDCPyLxSSISFskLQfukiqf2lr87kQq/oF27sAr3sR3Jqh4qzflM2XLgjmZuRE
+OrHT6lvUemRyksA5qg==
+-----END CERTIFICATE-----
diff --git a/test-CA/ca-key.pem b/test-CA/ca-key.pem
new file mode 100644
index 000000000..a8a304241
--- /dev/null
+++ b/test-CA/ca-key.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIq0bLh96mWv4CAggA
+MBQGCCqGSIb3DQMHBAimZqUiELx13QSCBMgaLWrGFqveIzwQUsebS6FBdVq0lodz
+Vlekje8ycFDYSd21V9jPMwrSupZceeBQjCrpyLZ3oPkR+MvObmznev8XYcJzVCkF
+E9ApAaHZe248wWcu1/D7auHNG3GyZfvYS0c//Rs2OzMZfsUvX93RVullCRREvCYS
+qXhaO3ywFocndKRpSnkOBs2SRa0yc9POl4n4dwyKhsJUaMSmhPbJr9UBvCbXHZIA
+gLcSWzVon3EtZCSubMp9eo90G5MzIXEyPBTcIHwpyqRWTkaTUTq4R0c4/RTX+l7K
+OZuRIEeBEW6z009fSagymN/KEH3gUkg5pG6i1YWF63OVKTMGn+yQGWwYXwTyEGi5
+HZpD98wh3ycucmL93XLk+yYXQcTp1i+u4GaXNWGREQvNW6onCGeg6WWj1PrIsqoi
+TZ2pgQUJWPR1K3037hY0o9sakAkyYSyTPVvHOUcbf3+GhqGS1FsSNOxKRNpYm/3v
+Gf0SUN8BavPliK9NSU5JAbprr/hoL5o72dCX9DiOgwfW3HyD/gLh7sVyVBdAzTnE
+XFaYFnrb5QnqHbgWvaLbJUT5K7MW3OFLVConydYtYdaUl5z49OflhgnvYOPgTSUr
+k9c7exQjedAduPd8dXODh9l2g+QEXJoT+YYFEYHkQlsZgH1hCLXD1TmAeI4LMklb
+vPaGE8Ouj1pfbejdTNsqLfW0IiR/jZzEjRgqrueMf2VUjtqTZyPayc2rU4kOoKhv
+JzQ0wOFhgRztWJy2voRe+iYss3ToqZ7qLpjBfCTsxCJqbuaGeJWWSnOlDpSysgr+
+q4BvCzDcvf/0mKD2cQuJx/kynQMCcWB/VegRsQ24Y+3T7IU1w8ccmRfSZ93AwkAh
+MKJzKaVhD/gn9vUG/we18p7RMIc9pk1o2Z2Ru3mKjkO3QYRP6Y7yk0ah2JKrHIPf
+LWfPuHmtzHQXkY3RbVvxvwD/+qHm8ogXq52w8cpGhY5UwAEHrLLwypdBHccrAJjo
+bE13M/MrtTry/k8OMRqhhRzHUXBq6mLaWffCaP2SAVfJEez2iASvGJFvgy3bSkWY
+rwWMSfZKDkauwDMW5gpFrpeuqgD64LO72sN01riVDpaEyNODRCEEBGce+O+91R9K
+TLVgRYFsxClyZy1nynD66gkTepEm1yOgcdqV3651Os+TGm39jGYHy1k9mPz8ypqf
+8n8uw4nV3SbIwfpy4Z8onHixfc/Fugm7yQHW4dSuCpahyIJHom6Cq7SZfPuo9e3t
+8tqaxvK4U/dAXoimvN1eakH2FoVFIj3mk7OAKBgmDINH9GlzXPwRsTfiJSP4Xaod
+ouWIQLLeXQuuOc5VJd1Xex75o8ciSOomAS0uR4Fvk/2NkAm0EMddjZnuWLQaXPry
+JiUIgSx3w3yRq9RSQOxDRQpp2nP2roX7cyeGPzTmeujikExGTa3YBxuAShDLx5pt
+fpi0ol8H8ohDU4eV9pv96KRBG9e8sQf1zpGjeYLTFiN35IQxYJx3HTXp9/oFWkmA
+OdCEwggIKJ/RtgkWOWogTilQVA41p4XZr661fxoSE86sHXkZKn8IGnAKLFT46nWM
+IYVDalYUiSNZr+KbzmLIV3LmYE3mlqGI4vDvQtd9zQk/uatYBc2DetuTWPZHCEKS
+3Nk=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/test-CA/openssl.cnf b/test-CA/openssl.cnf
new file mode 100644
index 000000000..ab73071ba
--- /dev/null
+++ b/test-CA/openssl.cnf
@@ -0,0 +1,357 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# This definition stops the following lines choking if HOME isn't
+# defined.
+HOME			= .
+RANDFILE		= $ENV::HOME/.rnd
+
+# Extra OBJECT IDENTIFIER info:
+#oid_file		= $ENV::HOME/.oid
+oid_section		= new_oids
+
+# To use this configuration file with the "-extfile" option of the
+# "openssl x509" utility, name here the section containing the
+# X.509v3 extensions to use:
+# extensions		= 
+# (Alternatively, use a configuration file that has only
+# X.509v3 extensions in its main [= default] section.)
+
+[ new_oids ]
+
+# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
+# Add a simple OID like this:
+# testoid1=1.2.3.4
+# Or use config file substitution like this:
+# testoid2=${testoid1}.5.6
+
+# Policies used by the TSA examples.
+tsa_policy1 = 1.2.3.4.1
+tsa_policy2 = 1.2.3.4.5.6
+tsa_policy3 = 1.2.3.4.5.7
+
+####################################################################
+[ ca ]
+default_ca	= CA_default		# The default ca section
+
+####################################################################
+[ CA_default ]
+
+dir		= ./test-CA		# Where everything is kept
+certs		= $dir/certs		# Where the issued certs are kept
+crl_dir		= $dir/crl		# Where the issued crl are kept
+database	= $dir/index.txt	# database index file.
+#unique_subject	= no			# Set to 'no' to allow creation of
+					# several ctificates with same subject.
+new_certs_dir	= $dir/newcerts		# default place for new certs.
+
+certificate	= $dir/ca-cert.pem 	# The CA certificate
+serial		= $dir/serial.txt 		# The current serial number
+crlnumber	= $dir/crlnumber	# the current crl number
+					# must be commented out to leave a V1 CRL
+crl		= $dir/crl.pem 		# The current CRL
+private_key	= $dir/ca-key.pem# The private key
+RANDFILE	= $dir/.rand	# private random number file
+
+x509_extensions	= usr_cert		# The extentions to add to the cert
+
+# Comment out the following two lines for the "traditional"
+# (and highly broken) format.
+name_opt 	= ca_default		# Subject Name options
+cert_opt 	= ca_default		# Certificate field options
+
+# Extension copying option: use with caution.
+# copy_extensions = copy
+
+# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
+# so this is commented out by default to leave a V1 CRL.
+# crlnumber must also be commented out to leave a V1 CRL.
+# crl_extensions	= crl_ext
+
+default_days	= 365			# how long to certify for
+default_crl_days= 30			# how long before next CRL
+default_md	= default		# use public key default MD
+preserve	= no			# keep passed DN ordering
+
+# A few difference way of specifying how similar the request should look
+# For type CA, the listed attributes must be the same, and the optional
+# and supplied fields are just that :-)
+policy		= policy_match
+
+# For the CA policy
+[ policy_match ]
+countryName		= optional
+stateOrProvinceName	= optional
+organizationName	= match
+organizationalUnitName	= optional
+commonName		= supplied
+emailAddress		= optional
+
+# For the 'anything' policy
+# At this point in time, you must list all acceptable 'object'
+# types.
+[ policy_anything ]
+countryName		= optional
+stateOrProvinceName	= optional
+localityName		= optional
+organizationName	= optional
+organizationalUnitName	= optional
+commonName		= supplied
+emailAddress		= optional
+
+####################################################################
+[ req ]
+default_bits		= 2048
+default_keyfile 	= privkey.pem
+distinguished_name	= req_distinguished_name
+attributes		= req_attributes
+x509_extensions	= v3_ca	# The extentions to add to the self signed cert
+
+# Passwords for private keys if not present they will be prompted for
+# input_password = secret
+# output_password = secret
+
+# This sets a mask for permitted string types. There are several options. 
+# default: PrintableString, T61String, BMPString.
+# pkix	 : PrintableString, BMPString (PKIX recommendation before 2004)
+# utf8only: only UTF8Strings (PKIX recommendation after 2004).
+# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
+# MASK:XXXX a literal mask value.
+# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
+string_mask = utf8only
+
+# req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName			= Country Name (2 letter code)
+countryName_default		=
+
+stateOrProvinceName		= State or Province Name (full name)
+stateOrProvinceName_default	= Some-State
+
+localityName			= Locality Name (eg, city)
+
+0.organizationName		= Organization Name (eg, company)
+0.organizationName_default	= Apache Software Foundation
+
+organizationalUnitName		= Organizational Unit Name (eg, section)
+organizationalUnitName_default	= HttpComponents Project
+
+commonName			= Common Name (e.g. server FQDN or YOUR name)
+commonName_max			= 64
+
+emailAddress			= Email Address
+emailAddress_max		= 64
+
+# SET-ex3			= SET extension number 3
+
+[ req_attributes ]
+challengePassword		= A challenge password
+challengePassword_min		= 4
+challengePassword_max		= 20
+
+unstructuredName		= An optional company name
+
+[ usr_cert ]
+
+# These extensions are added when 'ca' signs a request.
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType			= server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment			= "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl		= http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This is required for TSA certificates.
+# extendedKeyUsage = critical,timeStamping
+
+[ v3_req ]
+
+# Extensions to add to a certificate request
+
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+[ v3_ca ]
+
+
+# Extensions for a typical CA
+
+
+# PKIX recommendation.
+
+subjectKeyIdentifier=hash
+
+authorityKeyIdentifier=keyid:always,issuer
+
+# This is what PKIX recommends but some broken software chokes on critical
+# extensions.
+#basicConstraints = critical,CA:true
+# So we do this instead.
+basicConstraints = CA:true
+
+# Key usage: this is typical for a CA certificate. However since it will
+# prevent it being used as an test self-signed certificate it is best
+# left out by default.
+# keyUsage = cRLSign, keyCertSign
+
+# Some might want this also
+# nsCertType = sslCA, emailCA
+
+# Include email address in subject alt name: another PKIX recommendation
+# subjectAltName=email:copy
+# Copy issuer details
+# issuerAltName=issuer:copy
+
+# DER hex encoding of an extension: beware experts only!
+# obj=DER:02:03
+# Where 'obj' is a standard or added object
+# You can even override a supported extension:
+# basicConstraints= critical, DER:30:03:01:01:FF
+
+[ crl_ext ]
+
+# CRL extensions.
+# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
+
+# issuerAltName=issuer:copy
+authorityKeyIdentifier=keyid:always
+
+[ proxy_cert_ext ]
+# These extensions should be added when creating a proxy certificate
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType			= server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment			= "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl		= http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This really needs to be in place for it to be a proxy certificate.
+proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
+
+####################################################################
+[ tsa ]
+
+default_tsa = tsa_config1	# the default TSA section
+
+[ tsa_config1 ]
+
+# These are used by the TSA reply generation only.
+dir		= ./demoCA		# TSA root directory
+serial		= $dir/tsaserial	# The current serial number (mandatory)
+crypto_device	= builtin		# OpenSSL engine to use for signing
+signer_cert	= $dir/tsacert.pem 	# The TSA signing certificate
+					# (optional)
+certs		= $dir/cacert.pem	# Certificate chain to include in reply
+					# (optional)
+signer_key	= $dir/private/tsakey.pem # The TSA private key (optional)
+
+default_policy	= tsa_policy1		# Policy if request did not specify it
+					# (optional)
+other_policies	= tsa_policy2, tsa_policy3	# acceptable policies (optional)
+digests		= md5, sha1		# Acceptable message digests (mandatory)
+accuracy	= secs:1, millisecs:500, microsecs:100	# (optional)
+clock_precision_digits  = 0	# number of digits after dot. (optional)
+ordering		= yes	# Is ordering defined for timestamps?
+				# (optional, default: no)
+tsa_name		= yes	# Must the TSA name be included in the reply?
+				# (optional, default: no)
+ess_cert_id_chain	= no	# Must the ESS cert id chain be included?
+				# (optional, default: no)