diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 187832dab4..6a88469d58 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -49,4 +49,4 @@ jobs:
distribution: 'temurin'
java-version: ${{ matrix.java }}
- name: Build with Maven
- run: mvn -V --file pom.xml --no-transfer-progress -DtrimStackTrace=false -P-use-toolchains
+ run: mvn -V --file pom.xml --no-transfer-progress -DtrimStackTrace=false -P-use-toolchains,docker
diff --git a/httpclient5-testing/docker/.dockerignore b/httpclient5-testing/docker/.dockerignore
deleted file mode 100644
index 2b0b58d567..0000000000
--- a/httpclient5-testing/docker/.dockerignore
+++ /dev/null
@@ -1,16 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-*/*/.svn
diff --git a/httpclient5-testing/docker/apache-httpd/Dockerfile b/httpclient5-testing/docker/apache-httpd/Dockerfile
deleted file mode 100644
index 6400aff4fb..0000000000
--- a/httpclient5-testing/docker/apache-httpd/Dockerfile
+++ /dev/null
@@ -1,45 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-FROM httpd:2.4
-MAINTAINER dev@hc.apache.org
-
-ENV httpd_home /usr/local/apache2
-ENV var_dir /var/httpd
-ENV www_dir ${var_dir}/www
-ENV private_dir ${www_dir}/private
-
-RUN apt-get update
-RUN apt-get install -y subversion
-
-RUN mkdir -p ${var_dir}
-RUN svn co --depth immediates http://svn.apache.org/repos/asf/httpcomponents/site ${www_dir}
-RUN svn up --set-depth infinity ${www_dir}/images
-RUN svn up --set-depth infinity ${www_dir}/css
-
-RUN mkdir ${httpd_home}/ssl
-COPY server-cert.pem ${httpd_home}/ssl/
-COPY server-key.pem ${httpd_home}/ssl/
-COPY httpd.conf ${httpd_home}/conf/
-COPY httpd-ssl.conf ${httpd_home}/conf/extra/
-
-RUN mkdir -p ${private_dir}
-# user: testuser; pwd: nopassword
-RUN echo "testuser:{SHA}0Ybo2sSKJNARW1aNCrLJ6Lguats=" > ${private_dir}/.htpasswd
-RUN echo "testuser:Restricted Files:73deccd22e07066db8c405e5364335f5" > ${private_dir}/.htpasswd_digest
-RUN echo "Big Secret" > ${private_dir}/big-secret.txt
-
-EXPOSE 8080
-EXPOSE 8443
diff --git a/httpclient5-testing/docker/docker-compose.yml b/httpclient5-testing/docker/docker-compose.yml
deleted file mode 100644
index d04c5e8b13..0000000000
--- a/httpclient5-testing/docker/docker-compose.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-version: '3.5'
-
-services:
- test-httpd:
- container_name: "my-httpclient-tests-httpd"
- image: "httpclient-tests-httpd:latest"
- ports:
- - "8080:8080"
- - "8443:8443"
- test-squid:
- container_name: "my-httpclient-tests-squid"
- image: "httpclient-tests-squid:latest"
- ports:
- - "8888:8888"
- - "8889:8889"
- links:
- - "test-httpd"
\ No newline at end of file
diff --git a/httpclient5-testing/docker/squid/Dockerfile b/httpclient5-testing/docker/squid/Dockerfile
deleted file mode 100644
index 60f4253e6e..0000000000
--- a/httpclient5-testing/docker/squid/Dockerfile
+++ /dev/null
@@ -1,29 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-FROM sameersbn/squid:3.3.8-22
-MAINTAINER dev@hc.apache.org
-
-ENV conf_dir /etc/squid3
-
-RUN apt-get update
-RUN apt-get install -y apache2-utils
-
-COPY squid.conf ${conf_dir}/
-
-RUN htpasswd -b -c ${conf_dir}/htpasswd squid nopassword
-
-EXPOSE 8888
-EXPOSE 8889
diff --git a/httpclient5-testing/pom.xml b/httpclient5-testing/pom.xml
index 863f8bad40..63377a25af 100644
--- a/httpclient5-testing/pom.xml
+++ b/httpclient5-testing/pom.xml
@@ -97,8 +97,43 @@
rxjava
test
+
+ org.testcontainers
+ testcontainers
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+
+ docker
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ ${hc.surefire.version}
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+
+
+
+
+
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/ApacheHTTPDSquidCompatibilityIT.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/ApacheHTTPDSquidCompatibilityIT.java
new file mode 100644
index 0000000000..66ef1cda97
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/ApacheHTTPDSquidCompatibilityIT.java
@@ -0,0 +1,326 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.testing.compatibility;
+
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.testing.compatibility.async.CachingHttpAsyncClientCompatibilityTest;
+import org.apache.hc.client5.testing.compatibility.async.HttpAsyncClientCompatibilityTest;
+import org.apache.hc.client5.testing.compatibility.async.HttpAsyncClientHttp1CompatibilityTest;
+import org.apache.hc.client5.testing.compatibility.sync.CachingHttpClientCompatibilityTest;
+import org.apache.hc.client5.testing.compatibility.sync.HttpClientCompatibilityTest;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+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 ApacheHTTPDSquidCompatibilityIT {
+
+ private static Network NETWORK = Network.newNetwork();
+ @Container
+ static final GenericContainer> HTTPD_CONTAINER = ContainerImages.apacheHttpD(NETWORK);
+ @Container
+ static final GenericContainer> SQUID = ContainerImages.squid(NETWORK);
+
+ static HttpHost targetContainerHost() {
+ return new HttpHost(URIScheme.HTTP.id, HTTPD_CONTAINER.getHost(), HTTPD_CONTAINER.getMappedPort(ContainerImages.HTTP_PORT));
+ }
+
+ static HttpHost targetInternalHost() {
+ return new HttpHost(URIScheme.HTTP.id, ContainerImages.WEB_SERVER, ContainerImages.HTTP_PORT);
+ }
+
+ static HttpHost targetContainerTlsHost() {
+ return new HttpHost(URIScheme.HTTPS.id, HTTPD_CONTAINER.getHost(), HTTPD_CONTAINER.getMappedPort(ContainerImages.HTTPS_PORT));
+ }
+
+ static HttpHost targetInternalTlsHost() {
+ return new HttpHost(URIScheme.HTTPS.id, ContainerImages.WEB_SERVER, ContainerImages.HTTPS_PORT);
+ }
+
+ static HttpHost proxyContainerHost() {
+ return new HttpHost(URIScheme.HTTP.id, SQUID.getHost(), SQUID.getMappedPort(ContainerImages.PROXY_PORT));
+ }
+
+ static HttpHost proxyPwProtectedContainerHost() {
+ return new HttpHost(URIScheme.HTTP.id, SQUID.getHost(), SQUID.getMappedPort(ContainerImages.PROXY_PW_PROTECTED_PORT));
+ }
+
+ @AfterAll
+ static void cleanup() {
+ SQUID.close();
+ HTTPD_CONTAINER.close();
+ NETWORK.close();
+ }
+
+ @Nested
+ @DisplayName("Classic client: HTTP/1.1, plain, direct connection")
+ class ClassicDirectHttp extends HttpClientCompatibilityTest {
+
+ public ClassicDirectHttp() throws Exception {
+ super(targetContainerHost(), null, null);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Classic client: HTTP/1.1, plain, connection via proxy")
+ class ClassicViaProxyHttp extends HttpClientCompatibilityTest {
+
+ public ClassicViaProxyHttp() throws Exception {
+ super(targetInternalHost(), proxyContainerHost(), null);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Classic client: HTTP/1.1, plain, connection via password protected proxy")
+ class ClassicViaPwProtectedProxyHttp extends HttpClientCompatibilityTest {
+
+ public ClassicViaPwProtectedProxyHttp() throws Exception {
+ super(targetInternalHost(), proxyPwProtectedContainerHost(), new UsernamePasswordCredentials("squid", "nopassword".toCharArray()));
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Classic client: HTTP/1.1, TLS, direct connection")
+ class ClassicDirectHttpTls extends HttpClientCompatibilityTest {
+
+ public ClassicDirectHttpTls() throws Exception {
+ super(targetContainerTlsHost(), null, null);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Classic client: HTTP/1.1, TLS, connection via proxy (tunnel)")
+ class ClassicViaProxyHttpTls extends HttpClientCompatibilityTest {
+
+ public ClassicViaProxyHttpTls() throws Exception {
+ super(targetInternalTlsHost(), proxyContainerHost(), null);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Classic client: HTTP/1.1, TLS, connection via password protected proxy (tunnel)")
+ class ClassicViaPwProtectedProxyHttpTls extends HttpClientCompatibilityTest {
+
+ public ClassicViaPwProtectedProxyHttpTls() throws Exception {
+ super(targetInternalTlsHost(), proxyPwProtectedContainerHost(), new UsernamePasswordCredentials("squid", "nopassword".toCharArray()));
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/1.1, plain, direct connection")
+ class AsyncDirectHttp1 extends HttpAsyncClientHttp1CompatibilityTest {
+
+ public AsyncDirectHttp1() throws Exception {
+ super(targetContainerHost(), null, null);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/1.1, plain, connection via proxy")
+ class AsyncViaProxyHttp1 extends HttpAsyncClientHttp1CompatibilityTest {
+
+ public AsyncViaProxyHttp1() throws Exception {
+ super(targetInternalHost(), proxyContainerHost(), null);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/1.1, plain, connection via password protected proxy")
+ class AsyncViaPwProtectedProxyHttp1 extends HttpAsyncClientHttp1CompatibilityTest {
+
+ public AsyncViaPwProtectedProxyHttp1() throws Exception {
+ super(targetInternalHost(), proxyPwProtectedContainerHost(), new UsernamePasswordCredentials("squid", "nopassword".toCharArray()));
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/1.1, TLS, direct connection")
+ class AsyncDirectHttp1Tls extends HttpAsyncClientHttp1CompatibilityTest {
+
+ public AsyncDirectHttp1Tls() throws Exception {
+ super(targetContainerTlsHost(), null, null);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/1.1, TLS, connection via proxy (tunnel)")
+ class AsyncViaProxyHttp1Tls extends HttpAsyncClientHttp1CompatibilityTest {
+
+ public AsyncViaProxyHttp1Tls() throws Exception {
+ super(targetInternalTlsHost(), proxyContainerHost(), null);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/1.1, TLS, connection via password protected proxy (tunnel)")
+ class AsyncViaPwProtectedProxyHttp1Tls extends HttpAsyncClientHttp1CompatibilityTest {
+
+ public AsyncViaPwProtectedProxyHttp1Tls() throws Exception {
+ super(targetInternalTlsHost(), proxyPwProtectedContainerHost(), new UsernamePasswordCredentials("squid", "nopassword".toCharArray()));
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/2, plain, direct connection")
+ class AsyncDirectHttp2 extends HttpAsyncClientCompatibilityTest {
+
+ public AsyncDirectHttp2() throws Exception {
+ super(HttpVersionPolicy.FORCE_HTTP_2, targetContainerHost(), null, null);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/2, TLS, direct connection")
+ class AsyncDirectHttp2Tls extends HttpAsyncClientCompatibilityTest {
+
+ public AsyncDirectHttp2Tls() throws Exception {
+ super(HttpVersionPolicy.FORCE_HTTP_2, targetContainerTlsHost(), null, null);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/2, TLS, connection via proxy (tunnel)")
+ class AsyncViaProxyHttp2Tls extends HttpAsyncClientCompatibilityTest {
+
+ public AsyncViaProxyHttp2Tls() throws Exception {
+ super(HttpVersionPolicy.FORCE_HTTP_2, targetInternalTlsHost(), proxyContainerHost(), null);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/2, TLS, connection via password protected proxy (tunnel)")
+ class AsyncViaPwProtectedProxyHttp2Tls extends HttpAsyncClientCompatibilityTest {
+
+ public AsyncViaPwProtectedProxyHttp2Tls() throws Exception {
+ super(HttpVersionPolicy.FORCE_HTTP_2, targetInternalTlsHost(), proxyPwProtectedContainerHost(), new UsernamePasswordCredentials("squid", "nopassword".toCharArray()));
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: protocol negotiate, TLS, connection via proxy (tunnel)")
+ class AsyncViaProxyHttpNegotiateTls extends HttpAsyncClientCompatibilityTest {
+
+ public AsyncViaProxyHttpNegotiateTls() throws Exception {
+ super(HttpVersionPolicy.NEGOTIATE, targetInternalTlsHost(), proxyContainerHost(), null);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: protocol negotiate, TLS, connection via password protected proxy (tunnel)")
+ class AsyncViaPwProtectedProxyHttpNegotiateTls extends HttpAsyncClientCompatibilityTest {
+
+ public AsyncViaPwProtectedProxyHttpNegotiateTls() throws Exception {
+ super(HttpVersionPolicy.NEGOTIATE, targetInternalTlsHost(), proxyPwProtectedContainerHost(), new UsernamePasswordCredentials("squid", "nopassword".toCharArray()));
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Classic client: HTTP/1.1, caching, plain, direct connection")
+ class ClassicCachingHttp extends CachingHttpClientCompatibilityTest {
+
+ public ClassicCachingHttp() throws Exception {
+ super(targetContainerHost());
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Classic client: HTTP/1.1, caching, TLS, direct connection")
+ class ClassicCachingHttpTls extends CachingHttpClientCompatibilityTest {
+
+ public ClassicCachingHttpTls() throws Exception {
+ super(targetContainerTlsHost());
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/1.1, caching, plain, direct connection")
+ class AsyncCachingHttp1 extends CachingHttpAsyncClientCompatibilityTest {
+
+ public AsyncCachingHttp1() throws Exception {
+ super(HttpVersionPolicy.FORCE_HTTP_1, targetContainerHost());
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/1.1, caching, TLS, direct connection")
+ class AsyncCachingHttp1Tls extends CachingHttpAsyncClientCompatibilityTest {
+
+ public AsyncCachingHttp1Tls() throws Exception {
+ super(HttpVersionPolicy.FORCE_HTTP_1, targetContainerTlsHost());
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client HTTP/2, caching, plain, direct connection")
+ class AsyncCachingHttp2 extends CachingHttpAsyncClientCompatibilityTest {
+
+ public AsyncCachingHttp2() throws Exception {
+ super(HttpVersionPolicy.FORCE_HTTP_2, targetContainerHost());
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Async client: HTTP/2, caching, TLS, direct connection")
+ class AsyncCachingHttp2Tls extends CachingHttpAsyncClientCompatibilityTest {
+
+ public AsyncCachingHttp2Tls() throws Exception {
+ super(HttpVersionPolicy.FORCE_HTTP_2, targetContainerTlsHost());
+ }
+
+ }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/ContainerImages.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/ContainerImages.java
new file mode 100644
index 0000000000..1c313249ea
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/ContainerImages.java
@@ -0,0 +1,119 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.testing.compatibility;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Random;
+
+import org.apache.hc.client5.http.utils.ByteArrayBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+import org.testcontainers.images.builder.ImageFromDockerfile;
+import org.testcontainers.images.builder.Transferable;
+
+public final class ContainerImages {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ContainerImages.class);
+
+ public final static String WEB_SERVER = "test-httpd";
+ public final static int HTTP_PORT = 8080;
+ public final static int HTTPS_PORT = 8443;
+ public final static String PROXY = "test-proxy";
+ public final static int PROXY_PORT = 8888;
+ public final static int PROXY_PW_PROTECTED_PORT = 8889;
+
+ static final byte[] BYTES = "0123456789ABCDEF".getBytes(StandardCharsets.UTF_8);
+
+ static byte[] randomData(final int max) {
+ final Random random = new Random();
+ random.setSeed(System.currentTimeMillis());
+ final int n = random.nextInt(max);
+ final ByteArrayBuilder builder = new ByteArrayBuilder();
+ for (int i = 0; i < n; i++) {
+ builder.append(BYTES);
+ }
+ return builder.toByteArray();
+ }
+
+ public static GenericContainer> apacheHttpD(final Network network) {
+ return new GenericContainer<>(new ImageFromDockerfile()
+ .withFileFromClasspath("server-cert.pem", "docker/server-cert.pem")
+ .withFileFromClasspath("server-key.pem", "docker/server-key.pem")
+ .withFileFromClasspath("httpd.conf", "docker/httpd/httpd.conf")
+ .withFileFromClasspath("httpd-ssl.conf", "docker/httpd/httpd-ssl.conf")
+ .withFileFromTransferable("111", Transferable.of(randomData(10240)))
+ .withFileFromTransferable("222", Transferable.of(randomData(10240)))
+ .withFileFromTransferable("333", Transferable.of(randomData(10240)))
+ .withDockerfileFromBuilder(builder ->
+ builder
+ .from("httpd:2.4")
+ .env("httpd_home", "/usr/local/apache2")
+ .env("var_dir", "/var/httpd")
+ .env("www_dir", "${var_dir}/www")
+ .env("private_dir", "${www_dir}/private")
+ .run("mkdir ${httpd_home}/ssl")
+ .copy("server-cert.pem", "${httpd_home}/ssl/")
+ .copy("server-key.pem", "${httpd_home}/ssl/")
+ .copy("httpd.conf", "${httpd_home}/conf/")
+ .copy("httpd-ssl.conf", "${httpd_home}/conf/extra/")
+ .copy("111", "${www_dir}/")
+ .copy("222", "${www_dir}/")
+ .copy("333", "${www_dir}/")
+ .run("mkdir -p ${private_dir}")
+ //# user: testuser; pwd: nopassword
+ .run("echo \"testuser:{SHA}0Ybo2sSKJNARW1aNCrLJ6Lguats=\" > ${private_dir}/.htpasswd")
+ .run("echo \"testuser:Restricted Files:73deccd22e07066db8c405e5364335f5\" > ${private_dir}/.htpasswd_digest")
+ .run("echo \"Big Secret\" > ${private_dir}/big-secret.txt")
+ .build()))
+ .withNetwork(network)
+ .withNetworkAliases(WEB_SERVER)
+ .withLogConsumer(new Slf4jLogConsumer(LOG))
+ .withExposedPorts(HTTP_PORT, HTTPS_PORT);
+ }
+
+ public static GenericContainer> squid(final Network network) {
+ return new GenericContainer<>(new ImageFromDockerfile()
+ .withFileFromClasspath("squid.conf", "docker/squid/squid.conf")
+ .withDockerfileFromBuilder(builder ->
+ builder
+ .from("sameersbn/squid:3.3.8-22")
+ .env("conf_dir", "/etc/squid3")
+ .copy("squid.conf", "${conf_dir}/")
+ //# user: squid; pwd: nopassword
+ .run("echo \"squid:\\$apr1\\$.5saX63T\\$cMSoCJPqEfUw9br6zBdSO0\" > ${conf_dir}/htpasswd")
+ .build()))
+ .withNetwork(network)
+ .withNetworkAliases(PROXY)
+ .withLogConsumer(new Slf4jLogConsumer(LOG))
+ .withExposedPorts(PROXY_PORT, PROXY_PW_PROTECTED_PORT);
+
+ }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/async/CachingHttpAsyncClientCompatibilityTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/async/CachingHttpAsyncClientCompatibilityTest.java
new file mode 100644
index 0000000000..647f32d4b4
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/async/CachingHttpAsyncClientCompatibilityTest.java
@@ -0,0 +1,179 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.testing.compatibility.async;
+
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
+import org.apache.hc.client5.http.cache.CacheResponseStatus;
+import org.apache.hc.client5.http.cache.HttpCacheContext;
+import org.apache.hc.client5.http.cache.HttpCacheEntry;
+import org.apache.hc.client5.http.cache.RequestCacheControl;
+import org.apache.hc.client5.http.cache.ResponseCacheControl;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.cache.CacheConfig;
+import org.apache.hc.client5.http.impl.cache.HeapResourceFactory;
+import org.apache.hc.client5.testing.extension.async.CachingHttpAsyncClientResource;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.util.Timeout;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public abstract class CachingHttpAsyncClientCompatibilityTest {
+
+ static final Timeout TIMEOUT = Timeout.ofSeconds(5);
+
+ private final HttpHost target;
+ @RegisterExtension
+ private final CachingHttpAsyncClientResource clientResource;
+
+ public CachingHttpAsyncClientCompatibilityTest(final HttpVersionPolicy versionPolicy, final HttpHost target) throws Exception {
+ this.target = target;
+ this.clientResource = new CachingHttpAsyncClientResource(versionPolicy);
+ this.clientResource.configure(builder -> builder
+ .setCacheConfig(CacheConfig.custom()
+ .setMaxObjectSize(10240 * 16)
+ .build())
+ .setResourceFactory(HeapResourceFactory.INSTANCE));
+ }
+
+ CloseableHttpAsyncClient client() {
+ return clientResource.client();
+ }
+
+ @Test
+ @Disabled
+ void test_options_ping() throws Exception {
+ final CloseableHttpAsyncClient client = client();
+ final HttpCacheContext context = HttpCacheContext.create();
+ final SimpleHttpRequest options = SimpleRequestBuilder.options()
+ .setHttpHost(target)
+ .setPath("*")
+ .build();
+ final Future future = client.execute(options, context, null);
+ final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ }
+
+ @Test
+ void test_get_from_cache() throws Exception {
+ final CloseableHttpAsyncClient client = client();
+ final String[] resources1 = {"/111", "/222"};
+ for (final String r: resources1) {
+ final SimpleHttpRequest httpGet1 = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath(r)
+ .build();
+ final HttpCacheContext context1 = HttpCacheContext.create();
+ final Future future1 = client.execute(httpGet1, context1, null);
+ final SimpleHttpResponse response1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response1.getCode());
+ Assertions.assertEquals(CacheResponseStatus.CACHE_MISS, context1.getCacheResponseStatus());
+ final ResponseCacheControl responseCacheControl1 = context1.getResponseCacheControl();
+ Assertions.assertNotNull(responseCacheControl1);
+ if (!r.equals("/333")) {
+ Assertions.assertEquals(600, responseCacheControl1.getMaxAge());
+ }
+ final HttpCacheEntry cacheEntry1 = context1.getCacheEntry();
+ Assertions.assertNotNull(cacheEntry1);
+
+ final SimpleHttpRequest httpGet2 = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath(r)
+ .build();
+ final HttpCacheContext context2 = HttpCacheContext.create();
+ final Future future2 = client.execute(httpGet2, context2, null);
+ final SimpleHttpResponse response2 = future2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response2.getCode());
+ Assertions.assertEquals(CacheResponseStatus.CACHE_HIT, context2.getCacheResponseStatus());
+ final ResponseCacheControl responseCacheControl2 = context2.getResponseCacheControl();
+ Assertions.assertNotNull(responseCacheControl2);
+ Assertions.assertEquals(600, responseCacheControl2.getMaxAge());
+ final HttpCacheEntry cacheEntry2 = context2.getCacheEntry();
+ Assertions.assertNotNull(cacheEntry2);
+ Assertions.assertSame(cacheEntry2, context1.getCacheEntry());
+
+ Thread.sleep(2000);
+
+ final SimpleHttpRequest httpGet3 = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath(r)
+ .build();
+ final HttpCacheContext context3 = HttpCacheContext.create();
+ context3.setRequestCacheControl(RequestCacheControl.builder()
+ .setMaxAge(0)
+ .build());
+ final Future future3 = client.execute(httpGet3, context3, null);
+ final SimpleHttpResponse response3 = future3.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response3.getCode());
+ Assertions.assertEquals(CacheResponseStatus.VALIDATED, context3.getCacheResponseStatus());
+ final HttpCacheEntry cacheEntry3 = context3.getCacheEntry();
+ Assertions.assertNotNull(cacheEntry3);
+ Assertions.assertNotSame(cacheEntry3, context1.getCacheEntry());
+ }
+ final String[] resources2 = {"/333"};
+ for (final String r: resources2) {
+ final SimpleHttpRequest httpGet1 = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath(r)
+ .build();
+ final HttpCacheContext context1 = HttpCacheContext.create();
+ final Future future1 = client.execute(httpGet1, context1, null);
+ final SimpleHttpResponse response1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response1.getCode());
+ Assertions.assertEquals(CacheResponseStatus.CACHE_MISS, context1.getCacheResponseStatus());
+ final ResponseCacheControl responseCacheControl1 = context1.getResponseCacheControl();
+ Assertions.assertNotNull(responseCacheControl1);
+ Assertions.assertEquals(-1, responseCacheControl1.getMaxAge());
+ final HttpCacheEntry cacheEntry1 = context1.getCacheEntry();
+ Assertions.assertNotNull(cacheEntry1);
+
+ final SimpleHttpRequest httpGet2 = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath(r)
+ .build();
+ final HttpCacheContext context2 = HttpCacheContext.create();
+ final Future future2 = client.execute(httpGet2, context2, null);
+ final SimpleHttpResponse response2 = future2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response2.getCode());
+ Assertions.assertEquals(CacheResponseStatus.VALIDATED, context2.getCacheResponseStatus());
+ final ResponseCacheControl responseCacheControl2 = context2.getResponseCacheControl();
+ Assertions.assertNotNull(responseCacheControl2);
+ Assertions.assertEquals(-1, responseCacheControl2.getMaxAge());
+ final HttpCacheEntry cacheEntry2 = context2.getCacheEntry();
+ Assertions.assertNotNull(cacheEntry2);
+ Assertions.assertNotSame(cacheEntry2, context1.getCacheEntry());
+ }
+ }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/async/HttpAsyncClientCompatibilityTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/async/HttpAsyncClientCompatibilityTest.java
new file mode 100644
index 0000000000..acfa492fde
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/async/HttpAsyncClientCompatibilityTest.java
@@ -0,0 +1,176 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.testing.compatibility.async;
+
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.ContextBuilder;
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.Credentials;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.testing.extension.async.HttpAsyncClientResource;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.util.Timeout;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public abstract class HttpAsyncClientCompatibilityTest {
+
+ static final Timeout TIMEOUT = Timeout.ofSeconds(5);
+
+ private final HttpVersionPolicy versionPolicy;
+ private final HttpHost target;
+ @RegisterExtension
+ private final HttpAsyncClientResource clientResource;
+ private final BasicCredentialsProvider credentialsProvider;
+
+ public HttpAsyncClientCompatibilityTest(
+ final HttpVersionPolicy versionPolicy,
+ final HttpHost target,
+ final HttpHost proxy,
+ final Credentials proxyCreds) throws Exception {
+ this.versionPolicy = versionPolicy;
+ this.target = target;
+ this.clientResource = new HttpAsyncClientResource(versionPolicy);
+ this.clientResource.configure(builder -> builder.setProxy(proxy));
+ this.credentialsProvider = new BasicCredentialsProvider();
+ if (proxy != null && proxyCreds != null) {
+ this.credentialsProvider.setCredentials(new AuthScope(proxy), proxyCreds);
+ }
+ }
+
+ CloseableHttpAsyncClient client() {
+ return clientResource.client();
+ }
+
+ HttpClientContext context() {
+ return ContextBuilder.create()
+ .useCredentialsProvider(credentialsProvider)
+ .build();
+ }
+
+ void addCredentials(final AuthScope authScope, final Credentials credentials) {
+ credentialsProvider.setCredentials(authScope, credentials);
+ }
+
+ void assertProtocolVersion(final HttpClientContext context) {
+ switch (versionPolicy) {
+ case FORCE_HTTP_1:
+ Assertions.assertEquals(HttpVersion.HTTP_1_1, context.getProtocolVersion());
+ break;
+ case FORCE_HTTP_2:
+ case NEGOTIATE:
+ Assertions.assertEquals(HttpVersion.HTTP_2, context.getProtocolVersion());
+ break;
+ default:
+ throw new IllegalStateException("Unexpected version policy: " + versionPolicy);
+ }
+ }
+
+ @Test
+ void test_sequential_gets() throws Exception {
+ final CloseableHttpAsyncClient client = client();
+ final HttpClientContext context = context();
+
+ final String[] requestUris = new String[] {"/111", "/222", "/333"};
+ for (final String requestUri: requestUris) {
+ final SimpleHttpRequest httpGet = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath(requestUri)
+ .build();
+ final Future future = client.execute(httpGet, context, null);
+ final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ assertProtocolVersion(context);
+ }
+ }
+
+ @Test
+ void test_auth_failure_wrong_auth_scope() throws Exception {
+ addCredentials(
+ new AuthScope("http", "otherhost", -1, "Restricted Files", null),
+ new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
+ final CloseableHttpAsyncClient client = client();
+ final HttpClientContext context = context();
+
+ final SimpleHttpRequest httpGetSecret = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/private/big-secret.txt")
+ .build();
+ final Future future = client.execute(httpGetSecret, context, null);
+ final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
+ assertProtocolVersion(context);
+ }
+
+ @Test
+ void test_auth_failure_wrong_auth_credentials() throws Exception {
+ addCredentials(
+ new AuthScope(target),
+ new UsernamePasswordCredentials("testuser", "wrong password".toCharArray()));
+ final CloseableHttpAsyncClient client = client();
+ final HttpClientContext context = context();
+
+ final SimpleHttpRequest httpGetSecret = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/private/big-secret.txt")
+ .build();
+ final Future future = client.execute(httpGetSecret, context, null);
+ final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
+ assertProtocolVersion(context);
+ }
+
+ @Test
+ void test_auth_success() throws Exception {
+ addCredentials(
+ new AuthScope(target),
+ new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
+ final CloseableHttpAsyncClient client = client();
+ final HttpClientContext context = context();
+
+ final SimpleHttpRequest httpGetSecret = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/private/big-secret.txt")
+ .build();
+ final Future future = client.execute(httpGetSecret, context, null);
+ final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ assertProtocolVersion(context);
+ }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/async/HttpAsyncClientHttp1CompatibilityTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/async/HttpAsyncClientHttp1CompatibilityTest.java
new file mode 100644
index 0000000000..901e655c4b
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/async/HttpAsyncClientHttp1CompatibilityTest.java
@@ -0,0 +1,78 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.testing.compatibility.async;
+
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.Credentials;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.HeaderElements;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public abstract class HttpAsyncClientHttp1CompatibilityTest extends HttpAsyncClientCompatibilityTest {
+
+ private final HttpHost target;
+
+ public HttpAsyncClientHttp1CompatibilityTest(
+ final HttpHost target,
+ final HttpHost proxy,
+ final Credentials proxyCreds) throws Exception {
+ super(HttpVersionPolicy.FORCE_HTTP_1, target, proxy, proxyCreds);
+ this.target = target;
+ }
+
+ @Test
+ void test_auth_success_no_keep_alive() throws Exception {
+ addCredentials(
+ new AuthScope(target),
+ new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
+ final CloseableHttpAsyncClient client = client();
+ final HttpClientContext context = context();
+
+ final SimpleHttpRequest httpGetSecret = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/private/big-secret.txt")
+ .addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE)
+ .build();
+ final Future future = client.execute(httpGetSecret, context, null);
+ final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ assertProtocolVersion(context);
+ }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/sync/CachingHttpClientCompatibilityTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/sync/CachingHttpClientCompatibilityTest.java
new file mode 100644
index 0000000000..ebcb7b40b8
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/sync/CachingHttpClientCompatibilityTest.java
@@ -0,0 +1,156 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.testing.compatibility.sync;
+
+import org.apache.hc.client5.http.cache.CacheResponseStatus;
+import org.apache.hc.client5.http.cache.HttpCacheContext;
+import org.apache.hc.client5.http.cache.HttpCacheEntry;
+import org.apache.hc.client5.http.cache.RequestCacheControl;
+import org.apache.hc.client5.http.cache.ResponseCacheControl;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.classic.methods.HttpOptions;
+import org.apache.hc.client5.http.impl.cache.CacheConfig;
+import org.apache.hc.client5.http.impl.cache.HeapResourceFactory;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.testing.extension.sync.CachingHttpClientResource;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public abstract class CachingHttpClientCompatibilityTest {
+
+ private final HttpHost target;
+ @RegisterExtension
+ private final CachingHttpClientResource clientResource;
+
+ public CachingHttpClientCompatibilityTest(final HttpHost target) throws Exception {
+ this.target = target;
+ this.clientResource = new CachingHttpClientResource();
+ this.clientResource.configure(builder -> builder
+ .setCacheConfig(CacheConfig.custom()
+ .setMaxObjectSize(10240 * 16)
+ .build())
+ .setResourceFactory(HeapResourceFactory.INSTANCE));
+ }
+
+ CloseableHttpClient client() {
+ return clientResource.client();
+ }
+
+ @Test
+ void test_options_ping() throws Exception {
+ final CloseableHttpClient client = client();
+ final HttpCacheContext context = HttpCacheContext.create();
+ final HttpOptions options = new HttpOptions("*");
+ try (ClassicHttpResponse response = client.executeOpen(target, options, context)) {
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ EntityUtils.consume(response.getEntity());
+ }
+ }
+
+ @Test
+ void test_get_from_cache() throws Exception {
+ final CloseableHttpClient client = client();
+ final String[] resources1 = {"/111", "/222"};
+ for (final String r : resources1) {
+ final HttpCacheContext context1 = HttpCacheContext.create();
+ final HttpGet httpGet1 = new HttpGet(r);
+ try (ClassicHttpResponse response = client.executeOpen(target, httpGet1, context1)) {
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ Assertions.assertEquals(CacheResponseStatus.CACHE_MISS, context1.getCacheResponseStatus());
+ final ResponseCacheControl responseCacheControl = context1.getResponseCacheControl();
+ Assertions.assertNotNull(responseCacheControl);
+ Assertions.assertEquals(600, responseCacheControl.getMaxAge());
+ final HttpCacheEntry cacheEntry = context1.getCacheEntry();
+ Assertions.assertNotNull(cacheEntry);
+ EntityUtils.consume(response.getEntity());
+ }
+ final HttpCacheContext context2 = HttpCacheContext.create();
+ final HttpGet httpGet2 = new HttpGet(r);
+ try (ClassicHttpResponse response = client.executeOpen(target, httpGet2, context2)) {
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ Assertions.assertEquals(CacheResponseStatus.CACHE_HIT, context2.getCacheResponseStatus());
+ final ResponseCacheControl responseCacheControl = context2.getResponseCacheControl();
+ Assertions.assertNotNull(responseCacheControl);
+ Assertions.assertEquals(600, responseCacheControl.getMaxAge());
+ final HttpCacheEntry cacheEntry = context2.getCacheEntry();
+ Assertions.assertNotNull(cacheEntry);
+ Assertions.assertSame(cacheEntry, context1.getCacheEntry());
+ EntityUtils.consume(response.getEntity());
+ }
+
+ Thread.sleep(2000);
+
+ final HttpGet httpGet3 = new HttpGet(r);
+ final HttpCacheContext context3 = HttpCacheContext.create();
+ context3.setRequestCacheControl(RequestCacheControl.builder()
+ .setMaxAge(0)
+ .build());
+ try (ClassicHttpResponse response = client.executeOpen(target, httpGet3, context3)) {
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ Assertions.assertEquals(CacheResponseStatus.VALIDATED, context3.getCacheResponseStatus());
+ final HttpCacheEntry cacheEntry = context3.getCacheEntry();
+ Assertions.assertNotNull(cacheEntry);
+ Assertions.assertNotSame(cacheEntry, context1.getCacheEntry());
+ EntityUtils.consume(response.getEntity());
+ }
+ }
+ final String[] resources2 = {"/333"};
+ for (final String r : resources2) {
+ final HttpCacheContext context1 = HttpCacheContext.create();
+ final HttpGet httpGet1 = new HttpGet(r);
+ try (ClassicHttpResponse response = client.executeOpen(target, httpGet1, context1)) {
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ Assertions.assertEquals(CacheResponseStatus.CACHE_MISS, context1.getCacheResponseStatus());
+ final ResponseCacheControl responseCacheControl = context1.getResponseCacheControl();
+ Assertions.assertNotNull(responseCacheControl);
+ Assertions.assertEquals(-1, responseCacheControl.getMaxAge());
+ final HttpCacheEntry cacheEntry = context1.getCacheEntry();
+ Assertions.assertNotNull(cacheEntry);
+ EntityUtils.consume(response.getEntity());
+ }
+ final HttpCacheContext context2 = HttpCacheContext.create();
+ final HttpGet httpGet2 = new HttpGet(r);
+ try (ClassicHttpResponse response = client.executeOpen(target, httpGet2, context2)) {
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ Assertions.assertEquals(CacheResponseStatus.VALIDATED, context2.getCacheResponseStatus());
+ final ResponseCacheControl responseCacheControl = context2.getResponseCacheControl();
+ Assertions.assertNotNull(responseCacheControl);
+ Assertions.assertEquals(-1, responseCacheControl.getMaxAge());
+ final HttpCacheEntry cacheEntry = context2.getCacheEntry();
+ Assertions.assertNotNull(cacheEntry);
+ Assertions.assertNotSame(cacheEntry, context1.getCacheEntry());
+ EntityUtils.consume(response.getEntity());
+ }
+ }
+ }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/sync/HttpClientCompatibilityTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/sync/HttpClientCompatibilityTest.java
new file mode 100644
index 0000000000..6db3469524
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/sync/HttpClientCompatibilityTest.java
@@ -0,0 +1,188 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.testing.compatibility.sync;
+
+import org.apache.hc.client5.http.ContextBuilder;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.Credentials;
+import org.apache.hc.client5.http.auth.CredentialsStore;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.classic.methods.HttpOptions;
+import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.testing.extension.sync.HttpClientResource;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public abstract class HttpClientCompatibilityTest {
+
+ private final HttpHost target;
+ @RegisterExtension
+ private final HttpClientResource clientResource;
+ private final CredentialsStore credentialsProvider;
+
+ public HttpClientCompatibilityTest(final HttpHost target, final HttpHost proxy, final Credentials proxyCreds) throws Exception {
+ this.target = target;
+ this.clientResource = new HttpClientResource();
+ this.clientResource.configure(builder -> builder.setProxy(proxy));
+ this.credentialsProvider = new BasicCredentialsProvider();
+ if (proxy != null && proxyCreds != null) {
+ this.addCredentials(new AuthScope(proxy), proxyCreds);
+ }
+ }
+
+ CloseableHttpClient client() {
+ return clientResource.client();
+ }
+
+ HttpClientContext context() {
+ return ContextBuilder.create()
+ .useCredentialsProvider(credentialsProvider)
+ .build();
+ }
+
+ void addCredentials(final AuthScope authScope, final Credentials credentials) {
+ credentialsProvider.setCredentials(authScope, credentials);
+ }
+
+ @Test
+ void test_options_ping() throws Exception {
+ final CloseableHttpClient client = client();
+ final HttpClientContext context = context();
+ final HttpOptions options = new HttpOptions("*");
+ try (ClassicHttpResponse response = client.executeOpen(target, options, context)) {
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ EntityUtils.consume(response.getEntity());
+ }
+ }
+
+ @Test
+ void test_get() throws Exception {
+ final CloseableHttpClient client = client();
+ final HttpClientContext context = context();
+ final String[] requestUris = new String[] { "/111", "/222", "/333" };
+ for (final String requestUri: requestUris) {
+ final ClassicHttpRequest request = ClassicRequestBuilder.get(requestUri)
+ .build();
+ try (ClassicHttpResponse response = client.executeOpen(target, request, context)) {
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ EntityUtils.consume(response.getEntity());
+ }
+ }
+ }
+
+ @Test
+ void test_get_connection_close() throws Exception {
+ final CloseableHttpClient client = client();
+ final HttpClientContext context = context();
+ final String[] requestUris = new String[] { "/111", "/222", "/333" };
+ for (final String requestUri: requestUris) {
+ final ClassicHttpRequest request = ClassicRequestBuilder.get(requestUri)
+ .addHeader(HttpHeaders.CONNECTION, "close")
+ .build();
+ try (ClassicHttpResponse response = client.executeOpen(target, request, context)) {
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ EntityUtils.consume(response.getEntity());
+ }
+ }
+ }
+
+ @Test
+ void test_wrong_target_auth_scope() throws Exception {
+ addCredentials(
+ new AuthScope("http", "otherhost", -1, "Restricted Files", null),
+ new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
+
+ final CloseableHttpClient client = client();
+ final HttpClientContext context = context();
+
+ final ClassicHttpRequest request = new HttpGet("/private/big-secret.txt");
+ try (ClassicHttpResponse response = client.executeOpen(target, request, context)) {
+ Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
+ EntityUtils.consume(response.getEntity());
+ }
+ }
+
+ @Test
+ void test_wrong_target_credentials() throws Exception {
+ addCredentials(
+ new AuthScope(target),
+ new UsernamePasswordCredentials("testuser", "wrong password".toCharArray()));
+
+ final CloseableHttpClient client = client();
+ final HttpClientContext context = context();
+
+ final ClassicHttpRequest request = new HttpGet("/private/big-secret.txt");
+ try (ClassicHttpResponse response = client.executeOpen(target, request, context)) {
+ Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
+ EntityUtils.consume(response.getEntity());
+ }
+ }
+
+ @Test
+ void test_correct_target_credentials() throws Exception {
+ addCredentials(
+ new AuthScope(target),
+ new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
+ final CloseableHttpClient client = client();
+ final HttpClientContext context = context();
+
+ final ClassicHttpRequest request = new HttpGet("/private/big-secret.txt");
+ try (ClassicHttpResponse response = client.executeOpen(target, request, context)) {
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ EntityUtils.consume(response.getEntity());
+ }
+ }
+
+ @Test
+ void test_correct_target_credentials_no_keep_alive() throws Exception {
+ addCredentials(
+ new AuthScope(target),
+ new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
+ final CloseableHttpClient client = client();
+ final HttpClientContext context = context();
+
+ final ClassicHttpRequest request = ClassicRequestBuilder.get("/private/big-secret.txt")
+ .addHeader(HttpHeaders.CONNECTION, "close")
+ .build();
+ try (ClassicHttpResponse response = client.executeOpen(target, request, context)) {
+ Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+ EntityUtils.consume(response.getEntity());
+ }
+ }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/CachingHttpAsyncClientResource.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/CachingHttpAsyncClientResource.java
new file mode 100644
index 0000000000..e997c87336
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/CachingHttpAsyncClientResource.java
@@ -0,0 +1,89 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.hc.client5.testing.extension.async;
+
+import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.function.Consumer;
+
+import org.apache.hc.client5.http.config.TlsConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.cache.CachingHttpAsyncClientBuilder;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.ssl.SSLContexts;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class CachingHttpAsyncClientResource implements AfterEachCallback {
+
+ private final CachingHttpAsyncClientBuilder clientBuilder;
+ private CloseableHttpAsyncClient client;
+
+ public CachingHttpAsyncClientResource(final HttpVersionPolicy versionPolicy) throws IOException {
+ this.clientBuilder = CachingHttpAsyncClientBuilder.create();
+ try {
+ this.clientBuilder
+ .setConnectionManager(PoolingAsyncClientConnectionManagerBuilder.create()
+ .setTlsStrategy(new DefaultClientTlsStrategy(SSLContexts.custom()
+ .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray())
+ .build()))
+ .setDefaultTlsConfig(TlsConfig.custom()
+ .setVersionPolicy(versionPolicy)
+ .build())
+ .build());
+ } catch (final CertificateException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ public void configure(final Consumer customizer) {
+ customizer.accept(clientBuilder);
+ }
+
+ @Override
+ public void afterEach(final ExtensionContext extensionContext) {
+ if (client != null) {
+ client.close(CloseMode.GRACEFUL);
+ }
+ }
+
+ public CloseableHttpAsyncClient client() {
+ if (client == null) {
+ client = clientBuilder.build();
+ client.start();
+ }
+ return client;
+ }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/HttpAsyncClientResource.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/HttpAsyncClientResource.java
new file mode 100644
index 0000000000..3fff3d597e
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/HttpAsyncClientResource.java
@@ -0,0 +1,89 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.hc.client5.testing.extension.async;
+
+import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.function.Consumer;
+
+import org.apache.hc.client5.http.config.TlsConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.ssl.SSLContexts;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class HttpAsyncClientResource implements AfterEachCallback {
+
+ private final HttpAsyncClientBuilder clientBuilder;
+ private CloseableHttpAsyncClient client;
+
+ public HttpAsyncClientResource(final HttpVersionPolicy versionPolicy) throws IOException {
+ try {
+ this.clientBuilder = HttpAsyncClients.custom()
+ .setConnectionManager(PoolingAsyncClientConnectionManagerBuilder.create()
+ .setTlsStrategy(new DefaultClientTlsStrategy(SSLContexts.custom()
+ .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray())
+ .build()))
+ .setDefaultTlsConfig(TlsConfig.custom()
+ .setVersionPolicy(versionPolicy)
+ .build())
+ .build());
+ } catch (final CertificateException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ public void configure(final Consumer customizer) {
+ customizer.accept(clientBuilder);
+ }
+
+ @Override
+ public void afterEach(final ExtensionContext extensionContext) {
+ if (client != null) {
+ client.close(CloseMode.GRACEFUL);
+ }
+ }
+
+ public CloseableHttpAsyncClient client() {
+ if (client == null) {
+ client = clientBuilder.build();
+ client.start();
+ }
+ return client;
+ }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/CachingHttpClientResource.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/CachingHttpClientResource.java
new file mode 100644
index 0000000000..57e58e9626
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/CachingHttpClientResource.java
@@ -0,0 +1,83 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.hc.client5.testing.extension.sync;
+
+import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.function.Consumer;
+
+import org.apache.hc.client5.http.impl.cache.CachingHttpClientBuilder;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.ssl.SSLContexts;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class CachingHttpClientResource implements AfterEachCallback {
+
+ private final CachingHttpClientBuilder clientBuilder;
+ private CloseableHttpClient client;
+
+ public CachingHttpClientResource() throws IOException {
+ this.clientBuilder = CachingHttpClientBuilder.create();
+ try {
+ this.clientBuilder
+ .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
+ .setTlsSocketStrategy(new DefaultClientTlsStrategy(SSLContexts.custom()
+ .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray())
+ .build()))
+ .build());
+ } catch (final CertificateException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ public void configure(final Consumer customizer) {
+ customizer.accept(clientBuilder);
+ }
+
+ @Override
+ public void afterEach(final ExtensionContext extensionContext) {
+ if (client != null) {
+ client.close(CloseMode.GRACEFUL);
+ }
+ }
+
+ public CloseableHttpClient client() {
+ if (client == null) {
+ client = clientBuilder.build();
+ }
+ return client;
+ }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/HttpClientResource.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/HttpClientResource.java
new file mode 100644
index 0000000000..40577fd378
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/HttpClientResource.java
@@ -0,0 +1,83 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.hc.client5.testing.extension.sync;
+
+import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.function.Consumer;
+
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.ssl.SSLContexts;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class HttpClientResource implements AfterEachCallback {
+
+ private final HttpClientBuilder clientBuilder;
+ private CloseableHttpClient client;
+
+ public HttpClientResource() throws IOException {
+ try {
+ this.clientBuilder = HttpClients.custom()
+ .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
+ .setTlsSocketStrategy(new DefaultClientTlsStrategy(SSLContexts.custom()
+ .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray())
+ .build()))
+ .build());
+ } catch (final CertificateException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ public void configure(final Consumer customizer) {
+ customizer.accept(clientBuilder);
+ }
+
+ @Override
+ public void afterEach(final ExtensionContext extensionContext) {
+ if (client != null) {
+ client.close(CloseMode.GRACEFUL);
+ }
+ }
+
+ public CloseableHttpClient client() {
+ if (client == null) {
+ client = clientBuilder.build();
+ }
+ return client;
+ }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/TestClientResources.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/TestClientResources.java
index c38c7fba1b..2dcb6356ec 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/TestClientResources.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/TestClientResources.java
@@ -29,7 +29,6 @@
import java.util.function.Consumer;
-import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.util.Asserts;
@@ -50,7 +49,6 @@ public class TestClientResources implements AfterEachCallback {
private final TestClientBuilder clientBuilder;
private TestServer server;
- private PoolingHttpClientConnectionManager connManager;
private TestClient client;
public TestClientResources(final URIScheme scheme, final ClientProtocolLevel clientProtocolLevel, final Timeout timeout) {
@@ -76,11 +74,6 @@ public void afterEach(final ExtensionContext extensionContext) {
if (client != null) {
client.close(CloseMode.GRACEFUL);
}
-
- if (connManager != null) {
- connManager.close(CloseMode.IMMEDIATE);
- }
-
if (server != null) {
server.shutdown(CloseMode.IMMEDIATE);
}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/CachingHttpAsyncClientCompatibilityTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/CachingHttpAsyncClientCompatibilityTest.java
deleted file mode 100644
index 683286dfd6..0000000000
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/CachingHttpAsyncClientCompatibilityTest.java
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * .
- *
- */
-package org.apache.hc.client5.testing.external;
-
-import java.util.Objects;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeoutException;
-
-import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
-import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
-import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
-import org.apache.hc.client5.http.cache.CacheResponseStatus;
-import org.apache.hc.client5.http.cache.HttpCacheContext;
-import org.apache.hc.client5.http.config.TlsConfig;
-import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
-import org.apache.hc.client5.http.impl.cache.CacheConfig;
-import org.apache.hc.client5.http.impl.cache.CachingHttpAsyncClients;
-import org.apache.hc.client5.http.impl.cache.HeapResourceFactory;
-import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
-import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
-import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
-import org.apache.hc.core5.http.HttpHeaders;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.HttpResponse;
-import org.apache.hc.core5.http.HttpStatus;
-import org.apache.hc.core5.http.HttpVersion;
-import org.apache.hc.core5.http2.HttpVersionPolicy;
-import org.apache.hc.core5.ssl.SSLContexts;
-import org.apache.hc.core5.util.TextUtils;
-import org.apache.hc.core5.util.TimeValue;
-import org.apache.hc.core5.util.Timeout;
-
-public class CachingHttpAsyncClientCompatibilityTest {
-
- public static void main(final String... args) throws Exception {
- final CachingHttpAsyncClientCompatibilityTest[] tests = new CachingHttpAsyncClientCompatibilityTest[] {
- new CachingHttpAsyncClientCompatibilityTest(
- HttpVersion.HTTP_1_1, new HttpHost("http", "localhost", 8080)),
- new CachingHttpAsyncClientCompatibilityTest(
- HttpVersion.HTTP_2_0, new HttpHost("http", "localhost", 8080))
- };
- for (final CachingHttpAsyncClientCompatibilityTest test: tests) {
- try {
- test.execute();
- } finally {
- test.shutdown();
- }
- }
- }
-
- private static final Timeout TIMEOUT = Timeout.ofSeconds(5);
-
- private final HttpVersion protocolVersion;
- private final HttpHost target;
- private final PoolingAsyncClientConnectionManager connManager;
- private final CloseableHttpAsyncClient client;
-
- CachingHttpAsyncClientCompatibilityTest(final HttpVersion protocolVersion, final HttpHost target) throws Exception {
- this.protocolVersion = protocolVersion;
- this.target = target;
- this.connManager = PoolingAsyncClientConnectionManagerBuilder.create()
- .setTlsStrategy(new DefaultClientTlsStrategy(SSLContexts.custom()
- .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray())
- .build()))
- .setDefaultTlsConfig(TlsConfig.custom()
- .setVersionPolicy(this.protocolVersion == HttpVersion.HTTP_2 ?
- HttpVersionPolicy.FORCE_HTTP_2 : HttpVersionPolicy.FORCE_HTTP_1)
- .build())
- .build();
- this.client = CachingHttpAsyncClients.custom()
- .setCacheConfig(CacheConfig.custom()
- .setMaxObjectSize(20480)
- .setHeuristicCachingEnabled(true)
- .build())
- .setResourceFactory(HeapResourceFactory.INSTANCE)
- .setConnectionManager(this.connManager)
- .build();
- }
-
- void shutdown() throws Exception {
- client.close();
- }
-
- enum TestResult { OK, NOK }
-
- private void logResult(final TestResult result,
- final HttpRequest request,
- final HttpResponse response,
- final String message) {
- final StringBuilder buf = new StringBuilder();
- buf.append(result);
- if (buf.length() == 2) {
- buf.append(" ");
- }
- buf.append(": ");
- if (response != null && response.getVersion() != null) {
- buf.append(response.getVersion()).append(" ");
- } else {
- buf.append(protocolVersion).append(" ");
- }
- buf.append(target);
- buf.append(": ");
- buf.append(request.getMethod()).append(" ").append(request.getRequestUri());
- if (message != null && !TextUtils.isBlank(message)) {
- buf.append(" -> ").append(message);
- }
- System.out.println(buf);
- }
-
- void execute() throws InterruptedException {
-
- client.start();
- // Initial ping
- {
- final HttpCacheContext context = HttpCacheContext.create();
- final SimpleHttpRequest options = SimpleRequestBuilder.options()
- .setHttpHost(target)
- .setPath("*")
- .build();
- final Future future = client.execute(options, context, null);
- try {
- final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- final int code = response.getCode();
- if (code == HttpStatus.SC_OK) {
- logResult(TestResult.OK, options, response, Objects.toString(response.getFirstHeader("server")));
- } else {
- logResult(TestResult.NOK, options, response, "(status " + code + ")");
- }
- } catch (final ExecutionException ex) {
- final Throwable cause = ex.getCause();
- logResult(TestResult.NOK, options, null, "(" + cause.getMessage() + ")");
- } catch (final TimeoutException ex) {
- logResult(TestResult.NOK, options, null, "(time out)");
- }
- }
-
- // GET from cache
- {
- connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
- final HttpCacheContext context = HttpCacheContext.create();
-
- final String[] links = {"/", "/css/hc-maven.css", "/images/logos/httpcomponents.png"};
-
- for (final String link: links) {
- final SimpleHttpRequest httpGet1 = SimpleRequestBuilder.get()
- .setHttpHost(target)
- .setPath(link)
- .build();
- final Future linkFuture1 = client.execute(httpGet1, context, null);
- try {
- final SimpleHttpResponse response = linkFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- final int code = response.getCode();
- final CacheResponseStatus cacheResponseStatus = context.getCacheResponseStatus();
- if (code == HttpStatus.SC_OK && cacheResponseStatus == CacheResponseStatus.CACHE_MISS) {
- logResult(TestResult.OK, httpGet1, response, "200, " + cacheResponseStatus);
- } else {
- logResult(TestResult.NOK, httpGet1, response, "(status " + code + ", " + cacheResponseStatus + ")");
- }
- } catch (final ExecutionException ex) {
- final Throwable cause = ex.getCause();
- logResult(TestResult.NOK, httpGet1, null, "(" + cause.getMessage() + ")");
- } catch (final TimeoutException ex) {
- logResult(TestResult.NOK, httpGet1, null, "(time out)");
- }
-
- final SimpleHttpRequest httpGet2 = SimpleRequestBuilder.get()
- .setHttpHost(target)
- .setPath(link)
- .build();
- final Future linkFuture2 = client.execute(httpGet2, context, null);
- try {
- final SimpleHttpResponse response = linkFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- final int code = response.getCode();
- final CacheResponseStatus cacheResponseStatus = context.getCacheResponseStatus();
- if (code == HttpStatus.SC_OK && cacheResponseStatus == CacheResponseStatus.CACHE_HIT) {
- logResult(TestResult.OK, httpGet2, response, "200, " + cacheResponseStatus);
- } else {
- logResult(TestResult.NOK, httpGet2, response, "(status " + code + ", " + cacheResponseStatus + ")");
- }
- } catch (final ExecutionException ex) {
- final Throwable cause = ex.getCause();
- logResult(TestResult.NOK, httpGet2, null, "(" + cause.getMessage() + ")");
- } catch (final TimeoutException ex) {
- logResult(TestResult.NOK, httpGet2, null, "(time out)");
- }
-
- Thread.sleep(2000);
-
- final SimpleHttpRequest httpGet3 = SimpleRequestBuilder.get()
- .setHttpHost(target)
- .setPath(link)
- .setHeader(HttpHeaders.CACHE_CONTROL, "max-age=0")
- .build();
- final Future linkFuture3 = client.execute(httpGet3, context, null);
- try {
- final SimpleHttpResponse response = linkFuture3.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- final int code = response.getCode();
- final CacheResponseStatus cacheResponseStatus = context.getCacheResponseStatus();
- if (code == HttpStatus.SC_OK && cacheResponseStatus == CacheResponseStatus.VALIDATED) {
- logResult(TestResult.OK, httpGet3, response, "200, " + cacheResponseStatus);
- } else {
- logResult(TestResult.NOK, httpGet3, response, "(status " + code + ", " + cacheResponseStatus + ")");
- }
- } catch (final ExecutionException ex) {
- final Throwable cause = ex.getCause();
- logResult(TestResult.NOK, httpGet3, null, "(" + cause.getMessage() + ")");
- } catch (final TimeoutException ex) {
- logResult(TestResult.NOK, httpGet3, null, "(time out)");
- }
- }
- }
- }
-
-}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/CachingHttpClientCompatibilityTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/CachingHttpClientCompatibilityTest.java
deleted file mode 100644
index c24a4c1fc5..0000000000
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/CachingHttpClientCompatibilityTest.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * .
- *
- */
-package org.apache.hc.client5.testing.external;
-
-import java.util.Objects;
-
-import javax.net.ssl.SSLContext;
-
-import org.apache.hc.client5.http.cache.CacheResponseStatus;
-import org.apache.hc.client5.http.cache.HttpCacheContext;
-import org.apache.hc.client5.http.classic.methods.HttpGet;
-import org.apache.hc.client5.http.classic.methods.HttpOptions;
-import org.apache.hc.client5.http.impl.cache.CacheConfig;
-import org.apache.hc.client5.http.impl.cache.CachingHttpClients;
-import org.apache.hc.client5.http.impl.cache.HeapResourceFactory;
-import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
-import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
-import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
-import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
-import org.apache.hc.core5.http.ClassicHttpResponse;
-import org.apache.hc.core5.http.HttpHeaders;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.HttpStatus;
-import org.apache.hc.core5.http.io.entity.EntityUtils;
-import org.apache.hc.core5.ssl.SSLContexts;
-import org.apache.hc.core5.util.TextUtils;
-import org.apache.hc.core5.util.TimeValue;
-
-public class CachingHttpClientCompatibilityTest {
-
- public static void main(final String... args) throws Exception {
- final CachingHttpClientCompatibilityTest[] tests = new CachingHttpClientCompatibilityTest[] {
- new CachingHttpClientCompatibilityTest(
- new HttpHost("http", "localhost", 8080))
- };
- for (final CachingHttpClientCompatibilityTest test: tests) {
- try {
- test.execute();
- } finally {
- test.shutdown();
- }
- }
- }
-
- private final HttpHost target;
- private final PoolingHttpClientConnectionManager connManager;
- private final CloseableHttpClient client;
-
- CachingHttpClientCompatibilityTest(final HttpHost target) throws Exception {
- this.target = target;
- final SSLContext sslContext = SSLContexts.custom()
- .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray()).build();
- this.connManager = PoolingHttpClientConnectionManagerBuilder.create()
- .setTlsSocketStrategy(new DefaultClientTlsStrategy(sslContext))
- .build();
- this.client = CachingHttpClients.custom()
- .setCacheConfig(CacheConfig.custom()
- .setMaxObjectSize(20480)
- .setHeuristicCachingEnabled(true)
- .build())
- .setResourceFactory(HeapResourceFactory.INSTANCE)
- .setConnectionManager(this.connManager)
- .build();
- }
-
- void shutdown() throws Exception {
- client.close();
- }
-
- enum TestResult { OK, NOK }
-
- private void logResult(final TestResult result, final HttpRequest request, final String message) {
- final StringBuilder buf = new StringBuilder();
- buf.append(result);
- if (buf.length() == 2) {
- buf.append(" ");
- }
- buf.append(": ").append(target);
- buf.append(": ");
- buf.append(request.getMethod()).append(" ").append(request.getRequestUri());
- if (message != null && !TextUtils.isBlank(message)) {
- buf.append(" -> ").append(message);
- }
- System.out.println(buf);
- }
-
- void execute() throws InterruptedException {
-
- // Initial ping
- {
- final HttpCacheContext context = HttpCacheContext.create();
- final HttpOptions options = new HttpOptions("*");
- try (final ClassicHttpResponse response = client.executeOpen(target, options, context)) {
- final int code = response.getCode();
- EntityUtils.consume(response.getEntity());
- if (code == HttpStatus.SC_OK) {
- logResult(TestResult.OK, options, Objects.toString(response.getFirstHeader("server")));
- } else {
- logResult(TestResult.NOK, options, "(status " + code + ")");
- }
- } catch (final Exception ex) {
- logResult(TestResult.NOK, options, "(" + ex.getMessage() + ")");
- }
- }
- // GET from cache
- {
- connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
-
- final String[] links = {"/", "/css/hc-maven.css", "/images/logos/httpcomponents.png"};
-
- final HttpCacheContext context = HttpCacheContext.create();
- for (final String link: links) {
- final HttpGet httpGet1 = new HttpGet(link);
- try (ClassicHttpResponse response = client.executeOpen(target, httpGet1, context)) {
- final int code = response.getCode();
- final CacheResponseStatus cacheResponseStatus = context.getCacheResponseStatus();
- EntityUtils.consume(response.getEntity());
- if (code == HttpStatus.SC_OK && cacheResponseStatus == CacheResponseStatus.CACHE_MISS) {
- logResult(TestResult.OK, httpGet1, "200, " + cacheResponseStatus);
- } else {
- logResult(TestResult.NOK, httpGet1, "(status " + code + ", " + cacheResponseStatus + ")");
- }
- } catch (final Exception ex) {
- logResult(TestResult.NOK, httpGet1, "(" + ex.getMessage() + ")");
- }
- final HttpGet httpGet2 = new HttpGet(link);
- try (ClassicHttpResponse response = client.executeOpen(target, httpGet2, context)) {
- final int code = response.getCode();
- final CacheResponseStatus cacheResponseStatus = context.getCacheResponseStatus();
- EntityUtils.consume(response.getEntity());
- if (code == HttpStatus.SC_OK && cacheResponseStatus == CacheResponseStatus.CACHE_HIT) {
- logResult(TestResult.OK, httpGet2, "200, " + cacheResponseStatus);
- } else {
- logResult(TestResult.NOK, httpGet2, "(status " + code + ", " + cacheResponseStatus + ")");
- }
- } catch (final Exception ex) {
- logResult(TestResult.NOK, httpGet2, "(" + ex.getMessage() + ")");
- }
-
- Thread.sleep(2000);
-
- final HttpGet httpGet3 = new HttpGet(link);
- httpGet3.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=0");
- try (ClassicHttpResponse response = client.executeOpen(target, httpGet3, context)) {
- final int code = response.getCode();
- final CacheResponseStatus cacheResponseStatus = context.getCacheResponseStatus();
- EntityUtils.consume(response.getEntity());
- if (code == HttpStatus.SC_OK && cacheResponseStatus == CacheResponseStatus.VALIDATED) {
- logResult(TestResult.OK, httpGet3, "200, " + cacheResponseStatus);
- } else {
- logResult(TestResult.NOK, httpGet3, "(status " + code + ", " + cacheResponseStatus + ")");
- }
- } catch (final Exception ex) {
- logResult(TestResult.NOK, httpGet3, "(" + ex.getMessage() + ")");
- }
- }
- }
- }
-
-}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/HttpAsyncClientCompatibilityTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/HttpAsyncClientCompatibilityTest.java
deleted file mode 100644
index acc3d7fc5b..0000000000
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/HttpAsyncClientCompatibilityTest.java
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * .
- *
- */
-package org.apache.hc.client5.testing.external;
-
-import java.util.Objects;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeoutException;
-
-import javax.net.ssl.SSLContext;
-
-import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
-import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
-import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
-import org.apache.hc.client5.http.auth.AuthScope;
-import org.apache.hc.client5.http.auth.Credentials;
-import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
-import org.apache.hc.client5.http.config.RequestConfig;
-import org.apache.hc.client5.http.config.TlsConfig;
-import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
-import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
-import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
-import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
-import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
-import org.apache.hc.core5.http.HeaderElements;
-import org.apache.hc.core5.http.HttpHeaders;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.HttpResponse;
-import org.apache.hc.core5.http.HttpStatus;
-import org.apache.hc.core5.http2.HttpVersionPolicy;
-import org.apache.hc.core5.ssl.SSLContexts;
-import org.apache.hc.core5.util.TextUtils;
-import org.apache.hc.core5.util.TimeValue;
-import org.apache.hc.core5.util.Timeout;
-
-public class HttpAsyncClientCompatibilityTest {
-
- public static void main(final String... args) throws Exception {
- final HttpAsyncClientCompatibilityTest[] tests = new HttpAsyncClientCompatibilityTest[] {
- new HttpAsyncClientCompatibilityTest(
- HttpVersionPolicy.FORCE_HTTP_1,
- new HttpHost("http", "localhost", 8080), null, null),
- new HttpAsyncClientCompatibilityTest(
- HttpVersionPolicy.FORCE_HTTP_1,
- new HttpHost("http", "test-httpd", 8080), new HttpHost("localhost", 8888), null),
- new HttpAsyncClientCompatibilityTest(
- HttpVersionPolicy.FORCE_HTTP_1,
- new HttpHost("http", "test-httpd", 8080), new HttpHost("localhost", 8889),
- new UsernamePasswordCredentials("squid", "nopassword".toCharArray())),
- new HttpAsyncClientCompatibilityTest(
- HttpVersionPolicy.FORCE_HTTP_1,
- new HttpHost("https", "localhost", 8443), null, null),
- new HttpAsyncClientCompatibilityTest(
- HttpVersionPolicy.FORCE_HTTP_1,
- new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8888), null),
- new HttpAsyncClientCompatibilityTest(
- HttpVersionPolicy.FORCE_HTTP_1,
- new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8889),
- new UsernamePasswordCredentials("squid", "nopassword".toCharArray())),
- new HttpAsyncClientCompatibilityTest(
- HttpVersionPolicy.FORCE_HTTP_2,
- new HttpHost("http", "localhost", 8080), null, null),
- new HttpAsyncClientCompatibilityTest(
- HttpVersionPolicy.FORCE_HTTP_2,
- new HttpHost("https", "localhost", 8443), null, null),
- new HttpAsyncClientCompatibilityTest(
- HttpVersionPolicy.NEGOTIATE,
- new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8888), null),
- new HttpAsyncClientCompatibilityTest(
- HttpVersionPolicy.NEGOTIATE,
- new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8889),
- new UsernamePasswordCredentials("squid", "nopassword".toCharArray())),
- new HttpAsyncClientCompatibilityTest(
- HttpVersionPolicy.FORCE_HTTP_2,
- new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8888), null),
- new HttpAsyncClientCompatibilityTest(
- HttpVersionPolicy.FORCE_HTTP_2,
- new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8889),
- new UsernamePasswordCredentials("squid", "nopassword".toCharArray()))
- };
- for (final HttpAsyncClientCompatibilityTest test: tests) {
- try {
- test.execute();
- } finally {
- test.shutdown();
- }
- }
- }
-
- private static final Timeout TIMEOUT = Timeout.ofSeconds(5);
-
- private final HttpVersionPolicy versionPolicy;
- private final HttpHost target;
- private final HttpHost proxy;
- private final BasicCredentialsProvider credentialsProvider;
- private final PoolingAsyncClientConnectionManager connManager;
- private final CloseableHttpAsyncClient client;
-
- HttpAsyncClientCompatibilityTest(
- final HttpVersionPolicy versionPolicy,
- final HttpHost target,
- final HttpHost proxy,
- final Credentials proxyCreds) throws Exception {
- this.versionPolicy = versionPolicy;
- this.target = target;
- this.proxy = proxy;
- this.credentialsProvider = new BasicCredentialsProvider();
- final RequestConfig requestConfig = RequestConfig.DEFAULT;
- if (proxy != null && proxyCreds != null) {
- this.credentialsProvider.setCredentials(new AuthScope(proxy), proxyCreds);
- }
- final SSLContext sslContext = SSLContexts.custom()
- .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray()).build();
- this.connManager = PoolingAsyncClientConnectionManagerBuilder.create()
- .setTlsStrategy(new DefaultClientTlsStrategy(sslContext))
- .setDefaultTlsConfig(TlsConfig.custom()
- .setVersionPolicy(versionPolicy)
- .build())
- .build();
- this.client = HttpAsyncClients.custom()
- .setConnectionManager(this.connManager)
- .setProxy(this.proxy)
- .setDefaultRequestConfig(requestConfig)
- .build();
- }
-
- void shutdown() throws Exception {
- client.close();
- }
-
- enum TestResult { OK, NOK }
-
- private void logResult(final TestResult result,
- final HttpRequest request,
- final HttpResponse response,
- final String message) {
- final StringBuilder buf = new StringBuilder();
- buf.append(result);
- if (buf.length() == 2) {
- buf.append(" ");
- }
- buf.append(": ");
- if (response != null) {
- buf.append(response.getVersion()).append(" ");
- } else {
- buf.append(versionPolicy).append(" ");
- }
- buf.append(target);
- if (proxy != null) {
- buf.append(" via ").append(proxy);
- }
- buf.append(": ");
- buf.append(request.getMethod()).append(" ").append(request.getRequestUri());
- if (message != null && !TextUtils.isBlank(message)) {
- buf.append(" -> ").append(message);
- }
- System.out.println(buf);
- }
-
- void execute() throws Exception {
-
- client.start();
- // Initial ping
- {
- final HttpClientContext context = HttpClientContext.create();
- context.setCredentialsProvider(credentialsProvider);
-
- final SimpleHttpRequest options = SimpleRequestBuilder.options()
- .setHttpHost(target)
- .setPath("*")
- .build();
- final Future future = client.execute(options, context, null);
- try {
- final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- final int code = response.getCode();
- if (code == HttpStatus.SC_OK) {
- logResult(TestResult.OK, options, response, Objects.toString(response.getFirstHeader("server")));
- } else {
- logResult(TestResult.NOK, options, response, "(status " + code + ")");
- }
- } catch (final ExecutionException ex) {
- final Throwable cause = ex.getCause();
- logResult(TestResult.NOK, options, null, "(" + cause.getMessage() + ")");
- } catch (final TimeoutException ex) {
- logResult(TestResult.NOK, options, null, "(time out)");
- }
- }
- // Basic GET requests
- {
- connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
- final HttpClientContext context = HttpClientContext.create();
- context.setCredentialsProvider(credentialsProvider);
-
- final String[] requestUris = new String[] {"/", "/news.html", "/status.html"};
- for (final String requestUri: requestUris) {
- final SimpleHttpRequest httpGet = SimpleRequestBuilder.get()
- .setHttpHost(target)
- .setPath(requestUri)
- .build();
- final Future future = client.execute(httpGet, context, null);
- try {
- final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- final int code = response.getCode();
- if (code == HttpStatus.SC_OK) {
- logResult(TestResult.OK, httpGet, response, "200");
- } else {
- logResult(TestResult.NOK, httpGet, response, "(status " + code + ")");
- }
- } catch (final ExecutionException ex) {
- final Throwable cause = ex.getCause();
- logResult(TestResult.NOK, httpGet, null, "(" + cause.getMessage() + ")");
- } catch (final TimeoutException ex) {
- logResult(TestResult.NOK, httpGet, null, "(time out)");
- }
- }
- }
- // Wrong target auth scope
- {
- connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
- credentialsProvider.setCredentials(
- new AuthScope("http", "otherhost", -1, "Restricted Files", null),
- new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
- final HttpClientContext context = HttpClientContext.create();
- context.setCredentialsProvider(credentialsProvider);
-
- final SimpleHttpRequest httpGetSecret = SimpleRequestBuilder.get()
- .setHttpHost(target)
- .setPath("/private/big-secret.txt")
- .build();
- final Future future = client.execute(httpGetSecret, context, null);
- try {
- final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- final int code = response.getCode();
- if (code == HttpStatus.SC_UNAUTHORIZED) {
- logResult(TestResult.OK, httpGetSecret, response, "401 (wrong target auth scope)");
- } else {
- logResult(TestResult.NOK, httpGetSecret, response, "(status " + code + ")");
- }
- } catch (final ExecutionException ex) {
- final Throwable cause = ex.getCause();
- logResult(TestResult.NOK, httpGetSecret, null, "(" + cause.getMessage() + ")");
- } catch (final TimeoutException ex) {
- logResult(TestResult.NOK, httpGetSecret, null, "(time out)");
- }
- }
- // Wrong target credentials
- {
- connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
- credentialsProvider.setCredentials(
- new AuthScope(target),
- new UsernamePasswordCredentials("testuser", "wrong password".toCharArray()));
- final HttpClientContext context = HttpClientContext.create();
- context.setCredentialsProvider(credentialsProvider);
-
- final SimpleHttpRequest httpGetSecret = SimpleRequestBuilder.get()
- .setHttpHost(target)
- .setPath("/private/big-secret.txt")
- .build();
- final Future future = client.execute(httpGetSecret, context, null);
- try {
- final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- final int code = response.getCode();
- if (code == HttpStatus.SC_UNAUTHORIZED) {
- logResult(TestResult.OK, httpGetSecret, response, "401 (wrong target creds)");
- } else {
- logResult(TestResult.NOK, httpGetSecret, response, "(status " + code + ")");
- }
- } catch (final ExecutionException ex) {
- final Throwable cause = ex.getCause();
- logResult(TestResult.NOK, httpGetSecret, null, "(" + cause.getMessage() + ")");
- } catch (final TimeoutException ex) {
- logResult(TestResult.NOK, httpGetSecret, null, "(time out)");
- }
- }
- // Correct target credentials
- {
- connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
- credentialsProvider.setCredentials(
- new AuthScope(target),
- new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
- final HttpClientContext context = HttpClientContext.create();
- context.setCredentialsProvider(credentialsProvider);
-
- final SimpleHttpRequest httpGetSecret = SimpleRequestBuilder.get()
- .setHttpHost(target)
- .setPath("/private/big-secret.txt")
- .build();
- final Future future = client.execute(httpGetSecret, context, null);
- try {
- final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- final int code = response.getCode();
- if (code == HttpStatus.SC_OK) {
- logResult(TestResult.OK, httpGetSecret, response, "200 (correct target creds)");
- } else {
- logResult(TestResult.NOK, httpGetSecret, response, "(status " + code + ")");
- }
- } catch (final ExecutionException ex) {
- final Throwable cause = ex.getCause();
- logResult(TestResult.NOK, httpGetSecret, null, "(" + cause.getMessage() + ")");
- } catch (final TimeoutException ex) {
- logResult(TestResult.NOK, httpGetSecret, null, "(time out)");
- }
- }
- // Correct target credentials (no keep-alive)
- if (versionPolicy == HttpVersionPolicy.FORCE_HTTP_1)
- {
- connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
- credentialsProvider.setCredentials(
- new AuthScope(target),
- new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
- final HttpClientContext context = HttpClientContext.create();
- context.setCredentialsProvider(credentialsProvider);
-
- final SimpleHttpRequest httpGetSecret = SimpleRequestBuilder.get()
- .setHttpHost(target)
- .setPath("/private/big-secret.txt")
- .build();
- httpGetSecret.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
- final Future future = client.execute(httpGetSecret, context, null);
- try {
- final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- final int code = response.getCode();
- if (code == HttpStatus.SC_OK) {
- logResult(TestResult.OK, httpGetSecret, response, "200 (correct target creds / no keep-alive)");
- } else {
- logResult(TestResult.NOK, httpGetSecret, response, "(status " + code + ")");
- }
- } catch (final ExecutionException ex) {
- final Throwable cause = ex.getCause();
- logResult(TestResult.NOK, httpGetSecret, null, "(" + cause.getMessage() + ")");
- } catch (final TimeoutException ex) {
- logResult(TestResult.NOK, httpGetSecret, null, "(time out)");
- }
- }
- }
-
-}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/HttpClientCompatibilityTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/HttpClientCompatibilityTest.java
deleted file mode 100644
index 0b73e9997c..0000000000
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/HttpClientCompatibilityTest.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * .
- *
- */
-package org.apache.hc.client5.testing.external;
-
-import java.util.Objects;
-
-import javax.net.ssl.SSLContext;
-
-import org.apache.hc.client5.http.auth.AuthScope;
-import org.apache.hc.client5.http.auth.Credentials;
-import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
-import org.apache.hc.client5.http.classic.methods.HttpGet;
-import org.apache.hc.client5.http.classic.methods.HttpOptions;
-import org.apache.hc.client5.http.config.RequestConfig;
-import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
-import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
-import org.apache.hc.client5.http.impl.classic.HttpClients;
-import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
-import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
-import org.apache.hc.core5.http.ClassicHttpResponse;
-import org.apache.hc.core5.http.HeaderElements;
-import org.apache.hc.core5.http.HttpHeaders;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.HttpStatus;
-import org.apache.hc.core5.http.io.entity.EntityUtils;
-import org.apache.hc.core5.ssl.SSLContexts;
-import org.apache.hc.core5.util.TextUtils;
-import org.apache.hc.core5.util.TimeValue;
-
-public class HttpClientCompatibilityTest {
-
- public static void main(final String... args) throws Exception {
- final HttpClientCompatibilityTest[] tests = new HttpClientCompatibilityTest[] {
- new HttpClientCompatibilityTest(
- new HttpHost("http", "localhost", 8080), null, null),
- new HttpClientCompatibilityTest(
- new HttpHost("http", "test-httpd", 8080), new HttpHost("localhost", 8888), null),
- new HttpClientCompatibilityTest(
- new HttpHost("http", "test-httpd", 8080), new HttpHost("localhost", 8889),
- new UsernamePasswordCredentials("squid", "nopassword".toCharArray())),
- new HttpClientCompatibilityTest(
- new HttpHost("https", "localhost", 8443), null, null),
- new HttpClientCompatibilityTest(
- new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8888), null),
- new HttpClientCompatibilityTest(
- new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8889),
- new UsernamePasswordCredentials("squid", "nopassword".toCharArray()))
- };
- for (final HttpClientCompatibilityTest test: tests) {
- try {
- test.execute();
- } finally {
- test.shutdown();
- }
- }
- }
-
- private final HttpHost target;
- private final HttpHost proxy;
- private final BasicCredentialsProvider credentialsProvider;
- private final PoolingHttpClientConnectionManager connManager;
- private final CloseableHttpClient client;
-
- HttpClientCompatibilityTest(
- final HttpHost target,
- final HttpHost proxy,
- final Credentials proxyCreds) throws Exception {
- this.target = target;
- this.proxy = proxy;
- this.credentialsProvider = new BasicCredentialsProvider();
- final RequestConfig requestConfig = RequestConfig.DEFAULT;
- if (proxy != null && proxyCreds != null) {
- this.credentialsProvider.setCredentials(new AuthScope(proxy), proxyCreds);
- }
- final SSLContext sslContext = SSLContexts.custom()
- .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray()).build();
- this.connManager = PoolingHttpClientConnectionManagerBuilder.create()
- .setTlsSocketStrategy(new DefaultClientTlsStrategy(sslContext))
- .build();
- this.client = HttpClients.custom()
- .setConnectionManager(this.connManager)
- .setProxy(this.proxy)
- .setDefaultRequestConfig(requestConfig)
- .build();
- }
-
- void shutdown() throws Exception {
- client.close();
- }
-
- enum TestResult { OK, NOK }
-
- private void logResult(final TestResult result, final HttpRequest request, final String message) {
- final StringBuilder buf = new StringBuilder();
- buf.append(result);
- if (buf.length() == 2) {
- buf.append(" ");
- }
- buf.append(": ").append(target);
- if (proxy != null) {
- buf.append(" via ").append(proxy);
- }
- buf.append(": ");
- buf.append(request.getMethod()).append(" ").append(request.getRequestUri());
- if (message != null && !TextUtils.isBlank(message)) {
- buf.append(" -> ").append(message);
- }
- System.out.println(buf);
- }
-
- void execute() {
-
- // Initial ping
- {
- final HttpClientContext context = HttpClientContext.create();
- context.setCredentialsProvider(credentialsProvider);
- final HttpOptions options = new HttpOptions("*");
- try (ClassicHttpResponse response = client.executeOpen(target, options, context)) {
- final int code = response.getCode();
- EntityUtils.consume(response.getEntity());
- if (code == HttpStatus.SC_OK) {
- logResult(TestResult.OK, options, Objects.toString(response.getFirstHeader("server")));
- } else {
- logResult(TestResult.NOK, options, "(status " + code + ")");
- }
- } catch (final Exception ex) {
- logResult(TestResult.NOK, options, "(" + ex.getMessage() + ")");
- }
- }
- // Basic GET requests
- {
- connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
- final HttpClientContext context = HttpClientContext.create();
- context.setCredentialsProvider(credentialsProvider);
- final String[] requestUris = new String[] {"/", "/news.html", "/status.html"};
- for (final String requestUri: requestUris) {
- final HttpGet httpGet = new HttpGet(requestUri);
- try (ClassicHttpResponse response = client.executeOpen(target, httpGet, context)) {
- final int code = response.getCode();
- EntityUtils.consume(response.getEntity());
- if (code == HttpStatus.SC_OK) {
- logResult(TestResult.OK, httpGet, "200");
- } else {
- logResult(TestResult.NOK, httpGet, "(status " + code + ")");
- }
- } catch (final Exception ex) {
- logResult(TestResult.NOK, httpGet, "(" + ex.getMessage() + ")");
- }
- }
- }
- // Wrong target auth scope
- {
- connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
- credentialsProvider.setCredentials(
- new AuthScope("http", "otherhost", -1, "Restricted Files", null),
- new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
- final HttpClientContext context = HttpClientContext.create();
- context.setCredentialsProvider(credentialsProvider);
-
- final HttpGet httpGetSecret = new HttpGet("/private/big-secret.txt");
- try (ClassicHttpResponse response = client.executeOpen(target, httpGetSecret, context)) {
- final int code = response.getCode();
- EntityUtils.consume(response.getEntity());
- if (code == HttpStatus.SC_UNAUTHORIZED) {
- logResult(TestResult.OK, httpGetSecret, "401 (wrong target auth scope)");
- } else {
- logResult(TestResult.NOK, httpGetSecret, "(status " + code + ")");
- }
- } catch (final Exception ex) {
- logResult(TestResult.NOK, httpGetSecret, "(" + ex.getMessage() + ")");
- }
- }
- // Wrong target credentials
- {
- connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
- credentialsProvider.setCredentials(
- new AuthScope(target),
- new UsernamePasswordCredentials("testuser", "wrong password".toCharArray()));
- final HttpClientContext context = HttpClientContext.create();
- context.setCredentialsProvider(credentialsProvider);
-
- final HttpGet httpGetSecret = new HttpGet("/private/big-secret.txt");
- try (ClassicHttpResponse response = client.executeOpen(target, httpGetSecret, context)) {
- final int code = response.getCode();
- EntityUtils.consume(response.getEntity());
- if (code == HttpStatus.SC_UNAUTHORIZED) {
- logResult(TestResult.OK, httpGetSecret, "401 (wrong target creds)");
- } else {
- logResult(TestResult.NOK, httpGetSecret, "(status " + code + ")");
- }
- } catch (final Exception ex) {
- logResult(TestResult.NOK, httpGetSecret, "(" + ex.getMessage() + ")");
- }
- }
- // Correct target credentials
- {
- connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
- credentialsProvider.setCredentials(
- new AuthScope(target),
- new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
- final HttpClientContext context = HttpClientContext.create();
- context.setCredentialsProvider(credentialsProvider);
-
- final HttpGet httpGetSecret = new HttpGet("/private/big-secret.txt");
- try (ClassicHttpResponse response = client.executeOpen(target, httpGetSecret, context)) {
- final int code = response.getCode();
- EntityUtils.consume(response.getEntity());
- if (code == HttpStatus.SC_OK) {
- logResult(TestResult.OK, httpGetSecret, "200 (correct target creds)");
- } else {
- logResult(TestResult.NOK, httpGetSecret, "(status " + code + ")");
- }
- } catch (final Exception ex) {
- logResult(TestResult.NOK, httpGetSecret, "(" + ex.getMessage() + ")");
- }
- }
- // Correct target credentials (no keep-alive)
- {
- connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
- credentialsProvider.setCredentials(
- new AuthScope(target),
- new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
- final HttpClientContext context = HttpClientContext.create();
- context.setCredentialsProvider(credentialsProvider);
-
- final HttpGet httpGetSecret = new HttpGet("/private/big-secret.txt");
- httpGetSecret.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
- try (ClassicHttpResponse response = client.executeOpen(target, httpGetSecret, context)) {
- final int code = response.getCode();
- EntityUtils.consume(response.getEntity());
- if (code == HttpStatus.SC_OK) {
- logResult(TestResult.OK, httpGetSecret, "200 (correct target creds / no keep-alive)");
- } else {
- logResult(TestResult.NOK, httpGetSecret, "(status " + code + ")");
- }
- } catch (final Exception ex) {
- logResult(TestResult.NOK, httpGetSecret, "(" + ex.getMessage() + ")");
- }
- }
- }
-
-}
diff --git a/httpclient5-testing/docker/BUILDING.txt b/httpclient5-testing/src/test/resources/docker/BUILDING.txt
similarity index 56%
rename from httpclient5-testing/docker/BUILDING.txt
rename to httpclient5-testing/src/test/resources/docker/BUILDING.txt
index 419a61d2e8..fdc7d90566 100644
--- a/httpclient5-testing/docker/BUILDING.txt
+++ b/httpclient5-testing/src/test/resources/docker/BUILDING.txt
@@ -1,26 +1,3 @@
-Building Docker containers for compatibility tests
-========================================================
-
-= Apache HTTPD 2.4 image
-
-Remark: omit sudo command if executing as root
----
-sudo docker build -t httpclient-tests-httpd apache-httpd
----
-
-= Squid 3.3 image
-
-Remark: omit sudo command if executing as root
----
-sudo docker build -t httpclient-tests-squid squid
----
-
-= Start containers
-
----
-sudo docker-compose up
----
-
= SSL key / cert material (optional)
# Issue a certificate request
diff --git a/httpclient5-testing/docker/apache-httpd/httpd-ssl.conf b/httpclient5-testing/src/test/resources/docker/httpd/httpd-ssl.conf
similarity index 99%
rename from httpclient5-testing/docker/apache-httpd/httpd-ssl.conf
rename to httpclient5-testing/src/test/resources/docker/httpd/httpd-ssl.conf
index 53477407ca..ea175bb2f5 100644
--- a/httpclient5-testing/docker/apache-httpd/httpd-ssl.conf
+++ b/httpclient5-testing/src/test/resources/docker/httpd/httpd-ssl.conf
@@ -160,6 +160,11 @@ DocumentRoot "/var/httpd/www"
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
+
+
+ Header set Cache-Control "max-age=600"
+
+
diff --git a/httpclient5-testing/docker/apache-httpd/httpd.conf b/httpclient5-testing/src/test/resources/docker/httpd/httpd.conf
similarity index 98%
rename from httpclient5-testing/docker/apache-httpd/httpd.conf
rename to httpclient5-testing/src/test/resources/docker/httpd/httpd.conf
index 93d05d4268..f9931ea71b 100644
--- a/httpclient5-testing/docker/apache-httpd/httpd.conf
+++ b/httpclient5-testing/src/test/resources/docker/httpd/httpd.conf
@@ -311,17 +311,11 @@ DocumentRoot "/var/httpd/www"
#
Require all granted
-
+
+ Header set Cache-Control "max-age=600"
+
-
-
- Header add Link ";rel=preload"
- Header add Link ";rel=preload"
- Header add Link ";rel=preload"
- Header add Link ";rel=preload"
- Header add Link ";rel=preload"
-
-
+
#
# DirectoryIndex: sets the file that Apache will serve if a directory
diff --git a/httpclient5-testing/docker/apache-httpd/index.txt b/httpclient5-testing/src/test/resources/docker/index.txt
similarity index 100%
rename from httpclient5-testing/docker/apache-httpd/index.txt
rename to httpclient5-testing/src/test/resources/docker/index.txt
diff --git a/httpclient5-testing/docker/apache-httpd/openssl.cnf b/httpclient5-testing/src/test/resources/docker/openssl.cnf
similarity index 100%
rename from httpclient5-testing/docker/apache-httpd/openssl.cnf
rename to httpclient5-testing/src/test/resources/docker/openssl.cnf
diff --git a/httpclient5-testing/docker/apache-httpd/serial b/httpclient5-testing/src/test/resources/docker/serial
similarity index 100%
rename from httpclient5-testing/docker/apache-httpd/serial
rename to httpclient5-testing/src/test/resources/docker/serial
diff --git a/httpclient5-testing/docker/apache-httpd/server-cert.pem b/httpclient5-testing/src/test/resources/docker/server-cert.pem
similarity index 100%
rename from httpclient5-testing/docker/apache-httpd/server-cert.pem
rename to httpclient5-testing/src/test/resources/docker/server-cert.pem
diff --git a/httpclient5-testing/docker/apache-httpd/server-key.pem b/httpclient5-testing/src/test/resources/docker/server-key.pem
similarity index 100%
rename from httpclient5-testing/docker/apache-httpd/server-key.pem
rename to httpclient5-testing/src/test/resources/docker/server-key.pem
diff --git a/httpclient5-testing/docker/squid/squid.conf b/httpclient5-testing/src/test/resources/docker/squid/squid.conf
similarity index 100%
rename from httpclient5-testing/docker/squid/squid.conf
rename to httpclient5-testing/src/test/resources/docker/squid/squid.conf
diff --git a/pom.xml b/pom.xml
index d70eca2881..5b0bff4b95 100644
--- a/pom.xml
+++ b/pom.xml
@@ -74,9 +74,9 @@
4.11.0
1
2.2.21
+ 1.20.2
5.3
javax.net.ssl.SSLEngine,javax.net.ssl.SSLParameters,java.nio.ByteBuffer,java.nio.CharBuffer
- 0.15.4
@@ -182,6 +182,16 @@
${hamcrest.version}
test
+
+ org.testcontainers
+ testcontainers
+ ${testcontainers.version}
+
+
+ org.testcontainers
+ junit-jupiter
+ ${testcontainers.version}
+