View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.testing.external;
28  
29  import java.util.Objects;
30  import java.util.concurrent.ExecutionException;
31  import java.util.concurrent.Future;
32  import java.util.concurrent.TimeoutException;
33  
34  import javax.net.ssl.SSLContext;
35  
36  import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
37  import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
38  import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
39  import org.apache.hc.client5.http.auth.AuthScope;
40  import org.apache.hc.client5.http.auth.Credentials;
41  import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
42  import org.apache.hc.client5.http.config.RequestConfig;
43  import org.apache.hc.client5.http.config.TlsConfig;
44  import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
45  import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
46  import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
47  import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
48  import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
49  import org.apache.hc.client5.http.protocol.HttpClientContext;
50  import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
51  import org.apache.hc.core5.http.HeaderElements;
52  import org.apache.hc.core5.http.HttpHeaders;
53  import org.apache.hc.core5.http.HttpHost;
54  import org.apache.hc.core5.http.HttpRequest;
55  import org.apache.hc.core5.http.HttpResponse;
56  import org.apache.hc.core5.http.HttpStatus;
57  import org.apache.hc.core5.http2.HttpVersionPolicy;
58  import org.apache.hc.core5.ssl.SSLContexts;
59  import org.apache.hc.core5.util.TextUtils;
60  import org.apache.hc.core5.util.TimeValue;
61  import org.apache.hc.core5.util.Timeout;
62  
63  public class HttpAsyncClientCompatibilityTest {
64  
65      public static void main(final String... args) throws Exception {
66          final HttpAsyncClientCompatibilityTest[] tests = new HttpAsyncClientCompatibilityTest[] {
67                  new HttpAsyncClientCompatibilityTest(
68                          HttpVersionPolicy.FORCE_HTTP_1,
69                          new HttpHost("http", "localhost", 8080), null, null),
70                  new HttpAsyncClientCompatibilityTest(
71                          HttpVersionPolicy.FORCE_HTTP_1,
72                          new HttpHost("http", "test-httpd", 8080), new HttpHost("localhost", 8888), null),
73                  new HttpAsyncClientCompatibilityTest(
74                          HttpVersionPolicy.FORCE_HTTP_1,
75                          new HttpHost("http", "test-httpd", 8080), new HttpHost("localhost", 8889),
76                          new UsernamePasswordCredentials("squid", "nopassword".toCharArray())),
77                  new HttpAsyncClientCompatibilityTest(
78                          HttpVersionPolicy.FORCE_HTTP_1,
79                          new HttpHost("https", "localhost", 8443), null, null),
80                  new HttpAsyncClientCompatibilityTest(
81                          HttpVersionPolicy.FORCE_HTTP_1,
82                          new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8888), null),
83                  new HttpAsyncClientCompatibilityTest(
84                          HttpVersionPolicy.FORCE_HTTP_1,
85                          new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8889),
86                          new UsernamePasswordCredentials("squid", "nopassword".toCharArray())),
87                  new HttpAsyncClientCompatibilityTest(
88                          HttpVersionPolicy.FORCE_HTTP_2,
89                          new HttpHost("http", "localhost", 8080), null, null),
90                  new HttpAsyncClientCompatibilityTest(
91                          HttpVersionPolicy.FORCE_HTTP_2,
92                          new HttpHost("https", "localhost", 8443), null, null),
93                  new HttpAsyncClientCompatibilityTest(
94                          HttpVersionPolicy.NEGOTIATE,
95                          new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8888), null),
96                  new HttpAsyncClientCompatibilityTest(
97                          HttpVersionPolicy.NEGOTIATE,
98                          new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8889),
99                          new UsernamePasswordCredentials("squid", "nopassword".toCharArray())),
100                 new HttpAsyncClientCompatibilityTest(
101                         HttpVersionPolicy.FORCE_HTTP_2,
102                         new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8888), null),
103                 new HttpAsyncClientCompatibilityTest(
104                         HttpVersionPolicy.FORCE_HTTP_2,
105                         new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8889),
106                         new UsernamePasswordCredentials("squid", "nopassword".toCharArray()))
107         };
108         for (final HttpAsyncClientCompatibilityTest test: tests) {
109             try {
110                 test.execute();
111             } finally {
112                 test.shutdown();
113             }
114         }
115     }
116 
117     private static final Timeout TIMEOUT = Timeout.ofSeconds(5);
118 
119     private final HttpVersionPolicy versionPolicy;
120     private final HttpHost target;
121     private final HttpHost proxy;
122     private final BasicCredentialsProvider credentialsProvider;
123     private final PoolingAsyncClientConnectionManager connManager;
124     private final CloseableHttpAsyncClient client;
125 
126     HttpAsyncClientCompatibilityTest(
127             final HttpVersionPolicy versionPolicy,
128             final HttpHost target,
129             final HttpHost proxy,
130             final Credentials proxyCreds) throws Exception {
131         this.versionPolicy = versionPolicy;
132         this.target = target;
133         this.proxy = proxy;
134         this.credentialsProvider = new BasicCredentialsProvider();
135         final RequestConfig requestConfig = RequestConfig.DEFAULT;
136         if (proxy != null && proxyCreds != null) {
137             this.credentialsProvider.setCredentials(new AuthScope(proxy), proxyCreds);
138         }
139         final SSLContext sslContext = SSLContexts.custom()
140                 .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray()).build();
141         this.connManager = PoolingAsyncClientConnectionManagerBuilder.create()
142                 .setTlsStrategy(new DefaultClientTlsStrategy(sslContext))
143                 .setDefaultTlsConfig(TlsConfig.custom()
144                         .setVersionPolicy(versionPolicy)
145                         .build())
146                 .build();
147         this.client = HttpAsyncClients.custom()
148                 .setConnectionManager(this.connManager)
149                 .setProxy(this.proxy)
150                 .setDefaultRequestConfig(requestConfig)
151                 .build();
152     }
153 
154     void shutdown() throws Exception {
155         client.close();
156     }
157 
158     enum TestResult {OK, NOK}
159 
160     private void logResult(final TestResult result,
161                            final HttpRequest request,
162                            final HttpResponse response,
163                            final String message) {
164         final StringBuilder buf = new StringBuilder();
165         buf.append(result);
166         if (buf.length() == 2) {
167             buf.append(" ");
168         }
169         buf.append(": ");
170         if (response != null) {
171             buf.append(response.getVersion()).append(" ");
172         } else {
173             buf.append(versionPolicy).append(" ");
174         }
175         buf.append(target);
176         if (proxy != null) {
177             buf.append(" via ").append(proxy);
178         }
179         buf.append(": ");
180         buf.append(request.getMethod()).append(" ").append(request.getRequestUri());
181         if (message != null && !TextUtils.isBlank(message)) {
182             buf.append(" -> ").append(message);
183         }
184         System.out.println(buf);
185     }
186 
187     void execute() throws Exception {
188 
189         client.start();
190         // Initial ping
191         {
192             final HttpClientContext context = HttpClientContext.create();
193             context.setCredentialsProvider(credentialsProvider);
194 
195             final SimpleHttpRequest options = SimpleRequestBuilder.options()
196                     .setHttpHost(target)
197                     .setPath("*")
198                     .build();
199             final Future<SimpleHttpResponse> future = client.execute(options, context, null);
200             try {
201                 final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
202                 final int code = response.getCode();
203                 if (code == HttpStatus.SC_OK) {
204                     logResult(TestResult.OK, options, response, Objects.toString(response.getFirstHeader("server")));
205                 } else {
206                     logResult(TestResult.NOK, options, response, "(status " + code + ")");
207                 }
208             } catch (final ExecutionException ex) {
209                 final Throwable cause = ex.getCause();
210                 logResult(TestResult.NOK, options, null, "(" + cause.getMessage() + ")");
211             } catch (final TimeoutException ex) {
212                 logResult(TestResult.NOK, options, null, "(time out)");
213             }
214         }
215         // Basic GET requests
216         {
217             connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
218             final HttpClientContext context = HttpClientContext.create();
219             context.setCredentialsProvider(credentialsProvider);
220 
221             final String[] requestUris = new String[] {"/", "/news.html", "/status.html"};
222             for (final String requestUri: requestUris) {
223                 final SimpleHttpRequest httpGet = SimpleRequestBuilder.get()
224                         .setHttpHost(target)
225                         .setPath(requestUri)
226                         .build();
227                 final Future<SimpleHttpResponse> future = client.execute(httpGet, context, null);
228                 try {
229                     final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
230                     final int code = response.getCode();
231                     if (code == HttpStatus.SC_OK) {
232                         logResult(TestResult.OK, httpGet, response, "200");
233                     } else {
234                         logResult(TestResult.NOK, httpGet, response, "(status " + code + ")");
235                     }
236                 } catch (final ExecutionException ex) {
237                     final Throwable cause = ex.getCause();
238                     logResult(TestResult.NOK, httpGet, null, "(" + cause.getMessage() + ")");
239                 } catch (final TimeoutException ex) {
240                     logResult(TestResult.NOK, httpGet, null, "(time out)");
241                 }
242             }
243         }
244         // Wrong target auth scope
245         {
246             connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
247             credentialsProvider.setCredentials(
248                     new AuthScope("http", "otherhost", -1, "Restricted Files", null),
249                     new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
250             final HttpClientContext context = HttpClientContext.create();
251             context.setCredentialsProvider(credentialsProvider);
252 
253             final SimpleHttpRequest httpGetSecret = SimpleRequestBuilder.get()
254                     .setHttpHost(target)
255                     .setPath("/private/big-secret.txt")
256                     .build();
257             final Future<SimpleHttpResponse> future = client.execute(httpGetSecret, context, null);
258             try {
259                 final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
260                 final int code = response.getCode();
261                 if (code == HttpStatus.SC_UNAUTHORIZED) {
262                     logResult(TestResult.OK, httpGetSecret, response, "401 (wrong target auth scope)");
263                 } else {
264                     logResult(TestResult.NOK, httpGetSecret, response, "(status " + code + ")");
265                 }
266             } catch (final ExecutionException ex) {
267                 final Throwable cause = ex.getCause();
268                 logResult(TestResult.NOK, httpGetSecret, null, "(" + cause.getMessage() + ")");
269             } catch (final TimeoutException ex) {
270                 logResult(TestResult.NOK, httpGetSecret, null, "(time out)");
271             }
272         }
273         // Wrong target credentials
274         {
275             connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
276             credentialsProvider.setCredentials(
277                     new AuthScope(target),
278                     new UsernamePasswordCredentials("testuser", "wrong password".toCharArray()));
279             final HttpClientContext context = HttpClientContext.create();
280             context.setCredentialsProvider(credentialsProvider);
281 
282             final SimpleHttpRequest httpGetSecret = SimpleRequestBuilder.get()
283                     .setHttpHost(target)
284                     .setPath("/private/big-secret.txt")
285                     .build();
286             final Future<SimpleHttpResponse> future = client.execute(httpGetSecret, context, null);
287             try {
288                 final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
289                 final int code = response.getCode();
290                 if (code == HttpStatus.SC_UNAUTHORIZED) {
291                     logResult(TestResult.OK, httpGetSecret, response, "401 (wrong target creds)");
292                 } else {
293                     logResult(TestResult.NOK, httpGetSecret, response, "(status " + code + ")");
294                 }
295             } catch (final ExecutionException ex) {
296                 final Throwable cause = ex.getCause();
297                 logResult(TestResult.NOK, httpGetSecret, null, "(" + cause.getMessage() + ")");
298             } catch (final TimeoutException ex) {
299                 logResult(TestResult.NOK, httpGetSecret, null, "(time out)");
300             }
301         }
302         // Correct target credentials
303         {
304             connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
305             credentialsProvider.setCredentials(
306                     new AuthScope(target),
307                     new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
308             final HttpClientContext context = HttpClientContext.create();
309             context.setCredentialsProvider(credentialsProvider);
310 
311             final SimpleHttpRequest httpGetSecret = SimpleRequestBuilder.get()
312                     .setHttpHost(target)
313                     .setPath("/private/big-secret.txt")
314                     .build();
315             final Future<SimpleHttpResponse> future = client.execute(httpGetSecret, context, null);
316             try {
317                 final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
318                 final int code = response.getCode();
319                 if (code == HttpStatus.SC_OK) {
320                     logResult(TestResult.OK, httpGetSecret, response, "200 (correct target creds)");
321                 } else {
322                     logResult(TestResult.NOK, httpGetSecret, response, "(status " + code + ")");
323                 }
324             } catch (final ExecutionException ex) {
325                 final Throwable cause = ex.getCause();
326                 logResult(TestResult.NOK, httpGetSecret, null, "(" + cause.getMessage() + ")");
327             } catch (final TimeoutException ex) {
328                 logResult(TestResult.NOK, httpGetSecret, null, "(time out)");
329             }
330         }
331         // Correct target credentials (no keep-alive)
332         if (versionPolicy == HttpVersionPolicy.FORCE_HTTP_1)
333         {
334             connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
335             credentialsProvider.setCredentials(
336                     new AuthScope(target),
337                     new UsernamePasswordCredentials("testuser", "nopassword".toCharArray()));
338             final HttpClientContext context = HttpClientContext.create();
339             context.setCredentialsProvider(credentialsProvider);
340 
341             final SimpleHttpRequest httpGetSecret = SimpleRequestBuilder.get()
342                     .setHttpHost(target)
343                     .setPath("/private/big-secret.txt")
344                     .build();
345             httpGetSecret.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
346             final Future<SimpleHttpResponse> future = client.execute(httpGetSecret, context, null);
347             try {
348                 final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
349                 final int code = response.getCode();
350                 if (code == HttpStatus.SC_OK) {
351                     logResult(TestResult.OK, httpGetSecret, response, "200 (correct target creds / no keep-alive)");
352                 } else {
353                     logResult(TestResult.NOK, httpGetSecret, response, "(status " + code + ")");
354                 }
355             } catch (final ExecutionException ex) {
356                 final Throwable cause = ex.getCause();
357                 logResult(TestResult.NOK, httpGetSecret, null, "(" + cause.getMessage() + ")");
358             } catch (final TimeoutException ex) {
359                 logResult(TestResult.NOK, httpGetSecret, null, "(time out)");
360             }
361         }
362     }
363 
364 }