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.ArrayList;
30  import java.util.List;
31  import java.util.Objects;
32  import java.util.concurrent.ExecutionException;
33  import java.util.concurrent.Future;
34  import java.util.concurrent.TimeoutException;
35  import java.util.regex.Matcher;
36  import java.util.regex.Pattern;
37  
38  import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
39  import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
40  import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
41  import org.apache.hc.client5.http.cache.CacheResponseStatus;
42  import org.apache.hc.client5.http.cache.HttpCacheContext;
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.cache.CacheConfig;
46  import org.apache.hc.client5.http.impl.cache.CachingHttpAsyncClients;
47  import org.apache.hc.client5.http.impl.cache.HeapResourceFactory;
48  import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
49  import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
50  import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
51  import org.apache.hc.core5.http.Header;
52  import org.apache.hc.core5.http.HttpHost;
53  import org.apache.hc.core5.http.HttpRequest;
54  import org.apache.hc.core5.http.HttpStatus;
55  import org.apache.hc.core5.http.HttpVersion;
56  import org.apache.hc.core5.http2.HttpVersionPolicy;
57  import org.apache.hc.core5.ssl.SSLContexts;
58  import org.apache.hc.core5.util.TextUtils;
59  import org.apache.hc.core5.util.TimeValue;
60  import org.apache.hc.core5.util.Timeout;
61  
62  public class CachingHttpAsyncClientCompatibilityTest {
63  
64      public static void main(final String... args) throws Exception {
65          final CachingHttpAsyncClientCompatibilityTest[] tests = new CachingHttpAsyncClientCompatibilityTest[] {
66                  new CachingHttpAsyncClientCompatibilityTest(
67                          HttpVersion.HTTP_1_1, new HttpHost("http", "localhost", 8080)),
68                  new CachingHttpAsyncClientCompatibilityTest(
69                          HttpVersion.HTTP_2_0, new HttpHost("http", "localhost", 8080))
70          };
71          for (final CachingHttpAsyncClientCompatibilityTest test: tests) {
72              try {
73                  test.execute();
74              } finally {
75                  test.shutdown();
76              }
77          }
78      }
79  
80      private static final Timeout TIMEOUT = Timeout.ofSeconds(5);
81  
82      private final HttpVersion protocolVersion;
83      private final HttpHost target;
84      private final PoolingAsyncClientConnectionManager connManager;
85      private final CloseableHttpAsyncClient client;
86  
87      CachingHttpAsyncClientCompatibilityTest(final HttpVersion protocolVersion, final HttpHost target) throws Exception {
88          this.protocolVersion = protocolVersion;
89          this.target = target;
90          this.connManager = PoolingAsyncClientConnectionManagerBuilder.create()
91                  .setTlsStrategy(new DefaultClientTlsStrategy(SSLContexts.custom()
92                          .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray())
93                          .build()))
94                  .setDefaultTlsConfig(TlsConfig.custom()
95                          .setVersionPolicy(this.protocolVersion == HttpVersion.HTTP_2 ?
96                                  HttpVersionPolicy.FORCE_HTTP_2 : HttpVersionPolicy.FORCE_HTTP_1)
97                          .build())
98                  .build();
99          this.client = CachingHttpAsyncClients.custom()
100                 .setCacheConfig(CacheConfig.custom()
101                         .setMaxObjectSize(20480)
102                         .build())
103                 .setResourceFactory(HeapResourceFactory.INSTANCE)
104                 .setConnectionManager(this.connManager)
105                 .build();
106     }
107 
108 
109     void shutdown() throws Exception {
110         client.close();
111     }
112 
113     enum TestResult {OK, NOK}
114 
115     private void logResult(final TestResult result, final HttpRequest request, final String message) {
116         final StringBuilder buf = new StringBuilder();
117         buf.append(result);
118         if (buf.length() == 2) {
119             buf.append(" ");
120         }
121         buf.append(": ").append(target);
122         buf.append(": ");
123         buf.append(request.getMethod()).append(" ").append(request.getRequestUri());
124         if (message != null && !TextUtils.isBlank(message)) {
125             buf.append(" -> ").append(message);
126         }
127         System.out.println(buf);
128     }
129 
130     void execute() throws Exception {
131 
132         client.start();
133         // Initial ping
134         {
135             final HttpCacheContext context = HttpCacheContext.create();
136             final SimpleHttpRequest options = SimpleRequestBuilder.options()
137                     .setHttpHost(target)
138                     .setPath("*")
139                     .build();
140             final Future<SimpleHttpResponse> future = client.execute(options, context, null);
141             try {
142                 final SimpleHttpResponse response = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
143                 final int code = response.getCode();
144                 if (code == HttpStatus.SC_OK) {
145                     logResult(TestResult.OK, options, Objects.toString(response.getFirstHeader("server")));
146                 } else {
147                     logResult(TestResult.NOK, options, "(status " + code + ")");
148                 }
149             } catch (final ExecutionException ex) {
150                 final Throwable cause = ex.getCause();
151                 logResult(TestResult.NOK, options, "(" + cause.getMessage() + ")");
152             } catch (final TimeoutException ex) {
153                 logResult(TestResult.NOK, options, "(time out)");
154             }
155         }
156         // GET with links
157         {
158             connManager.closeIdle(TimeValue.NEG_ONE_MILLISECOND);
159             final HttpCacheContext context = HttpCacheContext.create();
160 
161             final Pattern linkPattern = Pattern.compile("^<(.*)>;rel=preload$");
162             final List<String> links = new ArrayList<>();
163             final SimpleHttpRequest getRoot1 = SimpleRequestBuilder.get()
164                     .setHttpHost(target)
165                     .setPath("/")
166                     .build();
167             final Future<SimpleHttpResponse> future1 = client.execute(getRoot1, context, null);
168             try {
169                 final SimpleHttpResponse response = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
170                 final int code = response.getCode();
171                 final CacheResponseStatus cacheResponseStatus = context.getCacheResponseStatus();
172                 if (code == HttpStatus.SC_OK && cacheResponseStatus == CacheResponseStatus.CACHE_MISS) {
173                     logResult(TestResult.OK, getRoot1, "200, " + cacheResponseStatus);
174                 } else {
175                     logResult(TestResult.NOK, getRoot1, "(status " + code + ", " + cacheResponseStatus + ")");
176                 }
177                 for (final Header header: response.getHeaders("Link")) {
178                     final Matcher matcher = linkPattern.matcher(header.getValue());
179                     if (matcher.matches()) {
180                         links.add(matcher.group(1));
181                     }
182                 }
183             } catch (final ExecutionException ex) {
184                 final Throwable cause = ex.getCause();
185                 logResult(TestResult.NOK, getRoot1, "(" + cause.getMessage() + ")");
186             } catch (final TimeoutException ex) {
187                 logResult(TestResult.NOK, getRoot1, "(time out)");
188             }
189             for (final String link: links) {
190                 final SimpleHttpRequest getLink = SimpleRequestBuilder.get()
191                         .setHttpHost(target)
192                         .setPath(link)
193                         .build();
194                 final Future<SimpleHttpResponse> linkFuture = client.execute(getLink, context, null);
195                 try {
196                     final SimpleHttpResponse response = linkFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
197                     final int code = response.getCode();
198                     final CacheResponseStatus cacheResponseStatus = context.getCacheResponseStatus();
199                     if (code == HttpStatus.SC_OK && cacheResponseStatus == CacheResponseStatus.CACHE_MISS) {
200                         logResult(TestResult.OK, getLink, "200, " + cacheResponseStatus);
201                     } else {
202                         logResult(TestResult.NOK, getLink, "(status " + code + ", " + cacheResponseStatus + ")");
203                     }
204                 } catch (final ExecutionException ex) {
205                     final Throwable cause = ex.getCause();
206                     logResult(TestResult.NOK, getLink, "(" + cause.getMessage() + ")");
207                 } catch (final TimeoutException ex) {
208                     logResult(TestResult.NOK, getLink, "(time out)");
209                 }
210             }
211 
212             final SimpleHttpRequest getRoot2 = SimpleRequestBuilder.get()
213                     .setHttpHost(target)
214                     .setPath("/")
215                     .build();
216             final Future<SimpleHttpResponse> future2 = client.execute(getRoot2, context, null);
217             try {
218                 final SimpleHttpResponse response = future2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
219                 final int code = response.getCode();
220                 final CacheResponseStatus cacheResponseStatus = context.getCacheResponseStatus();
221                 if (code == HttpStatus.SC_OK && cacheResponseStatus == CacheResponseStatus.VALIDATED) {
222                     logResult(TestResult.OK, getRoot2, "200, " + cacheResponseStatus);
223                 } else {
224                     logResult(TestResult.NOK, getRoot2, "(status " + code + ", " + cacheResponseStatus + ")");
225                 }
226             } catch (final ExecutionException ex) {
227                 final Throwable cause = ex.getCause();
228                 logResult(TestResult.NOK, getRoot2, "(" + cause.getMessage() + ")");
229             } catch (final TimeoutException ex) {
230                 logResult(TestResult.NOK, getRoot2, "(time out)");
231             }
232             for (final String link: links) {
233                 final SimpleHttpRequest getLink = SimpleRequestBuilder.get()
234                         .setHttpHost(target)
235                         .setPath(link)
236                         .build();
237                 final Future<SimpleHttpResponse> linkFuture = client.execute(getLink, context, null);
238                 try {
239                     final SimpleHttpResponse response = linkFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
240                     final int code = response.getCode();
241                     final CacheResponseStatus cacheResponseStatus = context.getCacheResponseStatus();
242                     if (code == HttpStatus.SC_OK && cacheResponseStatus == CacheResponseStatus.VALIDATED) {
243                         logResult(TestResult.OK, getLink, "200, " + cacheResponseStatus);
244                     } else {
245                         logResult(TestResult.NOK, getLink, "(status " + code + ", " + cacheResponseStatus + ")");
246                     }
247                 } catch (final ExecutionException ex) {
248                     final Throwable cause = ex.getCause();
249                     logResult(TestResult.NOK, getLink, "(" + cause.getMessage() + ")");
250                 } catch (final TimeoutException ex) {
251                     logResult(TestResult.NOK, getLink, "(time out)");
252                 }
253             }
254         }
255     }
256 
257 }