Migrated Docker based integration tests to TestContainers
This commit is contained in:
parent
cc6901798f
commit
0335839e22
|
@ -49,4 +49,4 @@ jobs:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: ${{ matrix.java }}
|
java-version: ${{ matrix.java }}
|
||||||
- name: Build with Maven
|
- 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
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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"
|
|
|
@ -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
|
|
|
@ -97,8 +97,43 @@
|
||||||
<artifactId>rxjava</artifactId>
|
<artifactId>rxjava</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>testcontainers</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>docker</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-failsafe-plugin</artifactId>
|
||||||
|
<version>${hc.surefire.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>integration-test</goal>
|
||||||
|
<goal>verify</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
|
||||||
<reporting>
|
<reporting>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|
|
@ -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
|
||||||
|
* <http://www.apache.org/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
* <http://www.apache.org/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
* <http://www.apache.org/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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<SimpleHttpResponse> 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<SimpleHttpResponse> 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<SimpleHttpResponse> 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<SimpleHttpResponse> 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<SimpleHttpResponse> 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<SimpleHttpResponse> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
* <http://www.apache.org/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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<SimpleHttpResponse> 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<SimpleHttpResponse> 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<SimpleHttpResponse> 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<SimpleHttpResponse> future = client.execute(httpGetSecret, context, null);
|
||||||
|
final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
|
||||||
|
Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
|
||||||
|
assertProtocolVersion(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
* <http://www.apache.org/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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<SimpleHttpResponse> future = client.execute(httpGetSecret, context, null);
|
||||||
|
final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
|
||||||
|
Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
|
||||||
|
assertProtocolVersion(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
* <http://www.apache.org/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
* <http://www.apache.org/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
* <http://www.apache.org/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<CachingHttpAsyncClientBuilder> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
* <http://www.apache.org/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<HttpAsyncClientBuilder> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* ====================================================================
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
* ====================================================================
|
||||||
|
*
|
||||||
|
* This software consists of voluntary contributions made by many
|
||||||
|
* individuals on behalf of the Apache Software Foundation. For more
|
||||||
|
* information on the Apache Software Foundation, please see
|
||||||
|
* <http://www.apache.org/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hc.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<CachingHttpClientBuilder> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* ====================================================================
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
* ====================================================================
|
||||||
|
*
|
||||||
|
* This software consists of voluntary contributions made by many
|
||||||
|
* individuals on behalf of the Apache Software Foundation. For more
|
||||||
|
* information on the Apache Software Foundation, please see
|
||||||
|
* <http://www.apache.org/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hc.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<HttpClientBuilder> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -29,7 +29,6 @@ package org.apache.hc.client5.testing.extension.sync;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
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.http.URIScheme;
|
||||||
import org.apache.hc.core5.io.CloseMode;
|
import org.apache.hc.core5.io.CloseMode;
|
||||||
import org.apache.hc.core5.util.Asserts;
|
import org.apache.hc.core5.util.Asserts;
|
||||||
|
@ -50,7 +49,6 @@ public class TestClientResources implements AfterEachCallback {
|
||||||
private final TestClientBuilder clientBuilder;
|
private final TestClientBuilder clientBuilder;
|
||||||
|
|
||||||
private TestServer server;
|
private TestServer server;
|
||||||
private PoolingHttpClientConnectionManager connManager;
|
|
||||||
private TestClient client;
|
private TestClient client;
|
||||||
|
|
||||||
public TestClientResources(final URIScheme scheme, final ClientProtocolLevel clientProtocolLevel, final Timeout timeout) {
|
public TestClientResources(final URIScheme scheme, final ClientProtocolLevel clientProtocolLevel, final Timeout timeout) {
|
||||||
|
@ -76,11 +74,6 @@ public class TestClientResources implements AfterEachCallback {
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
client.close(CloseMode.GRACEFUL);
|
client.close(CloseMode.GRACEFUL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connManager != null) {
|
|
||||||
connManager.close(CloseMode.IMMEDIATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (server != null) {
|
if (server != null) {
|
||||||
server.shutdown(CloseMode.IMMEDIATE);
|
server.shutdown(CloseMode.IMMEDIATE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
* <http://www.apache.org/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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<SimpleHttpResponse> 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<SimpleHttpResponse> 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<SimpleHttpResponse> 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<SimpleHttpResponse> 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)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
* <http://www.apache.org/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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() + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
* <http://www.apache.org/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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<SimpleHttpResponse> 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<SimpleHttpResponse> 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<SimpleHttpResponse> 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<SimpleHttpResponse> 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<SimpleHttpResponse> 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<SimpleHttpResponse> 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)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
* <http://www.apache.org/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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() + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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)
|
= SSL key / cert material (optional)
|
||||||
|
|
||||||
# Issue a certificate request
|
# Issue a certificate request
|
|
@ -160,6 +160,11 @@ DocumentRoot "/var/httpd/www"
|
||||||
Options Indexes FollowSymLinks
|
Options Indexes FollowSymLinks
|
||||||
AllowOverride None
|
AllowOverride None
|
||||||
Require all granted
|
Require all granted
|
||||||
|
|
||||||
|
<Files ~ "(111|222)">
|
||||||
|
Header set Cache-Control "max-age=600"
|
||||||
|
</Files>
|
||||||
|
|
||||||
</Directory>
|
</Directory>
|
||||||
|
|
||||||
|
|
|
@ -311,17 +311,11 @@ DocumentRoot "/var/httpd/www"
|
||||||
#
|
#
|
||||||
Require all granted
|
Require all granted
|
||||||
|
|
||||||
</Directory>
|
<Files ~ "(111|222)">
|
||||||
|
Header set Cache-Control "max-age=600"
|
||||||
|
</Files>
|
||||||
|
|
||||||
<IfModule headers_module>
|
</Directory>
|
||||||
<Location /index.html>
|
|
||||||
Header add Link "</css/site.css>;rel=preload"
|
|
||||||
Header add Link "</css/maven-theme.css>;rel=preload"
|
|
||||||
Header add Link "</css/maven-base.css>;rel=preload"
|
|
||||||
Header add Link "</css/hc-maven.css>;rel=preload"
|
|
||||||
Header add Link "</images/logos/httpcomponents.png>;rel=preload"
|
|
||||||
</Location>
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# DirectoryIndex: sets the file that Apache will serve if a directory
|
# DirectoryIndex: sets the file that Apache will serve if a directory
|
12
pom.xml
12
pom.xml
|
@ -74,9 +74,9 @@
|
||||||
<mockito.version>4.11.0</mockito.version>
|
<mockito.version>4.11.0</mockito.version>
|
||||||
<hc.stylecheck.version>1</hc.stylecheck.version>
|
<hc.stylecheck.version>1</hc.stylecheck.version>
|
||||||
<rxjava.version>2.2.21</rxjava.version>
|
<rxjava.version>2.2.21</rxjava.version>
|
||||||
|
<testcontainers.version>1.20.2</testcontainers.version>
|
||||||
<api.comparison.version>5.3</api.comparison.version>
|
<api.comparison.version>5.3</api.comparison.version>
|
||||||
<hc.animal-sniffer.signature.ignores>javax.net.ssl.SSLEngine,javax.net.ssl.SSLParameters,java.nio.ByteBuffer,java.nio.CharBuffer</hc.animal-sniffer.signature.ignores>
|
<hc.animal-sniffer.signature.ignores>javax.net.ssl.SSLEngine,javax.net.ssl.SSLParameters,java.nio.ByteBuffer,java.nio.CharBuffer</hc.animal-sniffer.signature.ignores>
|
||||||
<japicmp.version>0.15.4</japicmp.version>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
@ -182,6 +182,16 @@
|
||||||
<version>${hamcrest.version}</version>
|
<version>${hamcrest.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>testcontainers</artifactId>
|
||||||
|
<version>${testcontainers.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${testcontainers.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue