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.http.impl.cache;
28  
29  import java.net.URI;
30  import java.net.URISyntaxException;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Collections;
34  import java.util.Iterator;
35  import java.util.List;
36  
37  import org.apache.hc.client5.http.cache.HttpCacheEntry;
38  import org.apache.hc.client5.http.classic.methods.HttpGet;
39  import org.apache.hc.core5.http.Header;
40  import org.apache.hc.core5.http.HttpHeaders;
41  import org.apache.hc.core5.http.HttpHost;
42  import org.apache.hc.core5.http.HttpRequest;
43  import org.apache.hc.core5.http.message.BasicHeader;
44  import org.apache.hc.core5.http.message.BasicHeaderIterator;
45  import org.apache.hc.core5.http.message.BasicHttpRequest;
46  import org.apache.hc.core5.http.support.BasicRequestBuilder;
47  import org.junit.jupiter.api.Assertions;
48  import org.junit.jupiter.api.BeforeEach;
49  import org.junit.jupiter.api.Test;
50  
51  @SuppressWarnings({"boxing","static-access"}) // this is test code
52  public class TestCacheKeyGenerator {
53  
54      private CacheKeyGenerator extractor;
55  
56      @BeforeEach
57      public void setUp() throws Exception {
58          extractor = CacheKeyGenerator.INSTANCE;
59      }
60  
61      @Test
62      public void testGetRequestUri() {
63          Assertions.assertEquals("http://foo.example.com/stuff?huh",
64                  CacheKeyGenerator.getRequestUri(
65                          new HttpHost("bar.example.com"),
66                          new HttpGet("http://foo.example.com/stuff?huh")));
67  
68          Assertions.assertEquals("http://bar.example.com/stuff?huh",
69                  CacheKeyGenerator.getRequestUri(
70                          new HttpHost("bar.example.com"),
71                          new HttpGet("/stuff?huh")));
72  
73          Assertions.assertEquals("http://foo.example.com:8888/stuff?huh",
74                  CacheKeyGenerator.getRequestUri(
75                          new HttpHost("bar.example.com", 8080),
76                          new HttpGet("http://foo.example.com:8888/stuff?huh")));
77  
78          Assertions.assertEquals("https://bar.example.com:8443/stuff?huh",
79                  CacheKeyGenerator.getRequestUri(
80                          new HttpHost("https", "bar.example.com", 8443),
81                          new HttpGet("/stuff?huh")));
82  
83          Assertions.assertEquals("http://foo.example.com/",
84                  CacheKeyGenerator.getRequestUri(
85                          new HttpHost("bar.example.com"),
86                          new HttpGet("http://foo.example.com")));
87  
88          Assertions.assertEquals("http://bar.example.com/stuff?huh",
89                  CacheKeyGenerator.getRequestUri(
90                          new HttpHost("bar.example.com"),
91                          new HttpGet("stuff?huh")));
92      }
93  
94      @Test
95      public void testNormalizeRequestUri() throws URISyntaxException {
96          Assertions.assertEquals(URI.create("http://bar.example.com:80/stuff?huh"),
97                  CacheKeyGenerator.normalize(URI.create("//bar.example.com/stuff?huh")));
98  
99          Assertions.assertEquals(URI.create("http://bar.example.com:80/stuff?huh"),
100                 CacheKeyGenerator.normalize(URI.create("http://bar.example.com/stuff?huh")));
101 
102         Assertions.assertEquals(URI.create("http://bar.example.com:80/stuff?huh"),
103                 CacheKeyGenerator.normalize(URI.create("http://bar.example.com/stuff?huh#there")));
104 
105         Assertions.assertEquals(URI.create("http://bar.example.com:80/stuff?huh"),
106                 CacheKeyGenerator.normalize(URI.create("HTTP://BAR.example.com/p1/p2/../../stuff?huh")));
107     }
108 
109     @Test
110     public void testExtractsUriFromAbsoluteUriInRequest() {
111         final HttpHost host = new HttpHost("bar.example.com");
112         final HttpRequest req = new HttpGet("http://foo.example.com/");
113         Assertions.assertEquals("http://foo.example.com:80/", extractor.generateKey(host, req));
114     }
115 
116     @Test
117     public void testGetURIWithDefaultPortAndScheme() {
118         Assertions.assertEquals("http://www.comcast.net:80/", extractor.generateKey(
119                 new HttpHost("www.comcast.net"),
120                 new BasicHttpRequest("GET", "/")));
121 
122         Assertions.assertEquals("http://www.fancast.com:80/full_episodes", extractor.generateKey(
123                 new HttpHost("www.fancast.com"),
124                 new BasicHttpRequest("GET", "/full_episodes")));
125     }
126 
127     @Test
128     public void testGetURIWithDifferentScheme() {
129         Assertions.assertEquals("https://www.comcast.net:443/", extractor.generateKey(
130                 new HttpHost("https", "www.comcast.net", -1),
131                 new BasicHttpRequest("GET", "/")));
132 
133         Assertions.assertEquals("myhttp://www.fancast.com/full_episodes", extractor.generateKey(
134                 new HttpHost("myhttp", "www.fancast.com", -1),
135                 new BasicHttpRequest("GET", "/full_episodes")));
136     }
137 
138     @Test
139     public void testGetURIWithDifferentPort() {
140         Assertions.assertEquals("http://www.comcast.net:8080/", extractor.generateKey(
141                 new HttpHost("www.comcast.net", 8080),
142                 new BasicHttpRequest("GET", "/")));
143 
144         Assertions.assertEquals("http://www.fancast.com:9999/full_episodes", extractor.generateKey(
145                 new HttpHost("www.fancast.com", 9999),
146                 new BasicHttpRequest("GET", "/full_episodes")));
147     }
148 
149     @Test
150     public void testGetURIWithDifferentPortAndScheme() {
151         Assertions.assertEquals("https://www.comcast.net:8080/", extractor.generateKey(
152                 new HttpHost("https", "www.comcast.net", 8080),
153                 new BasicHttpRequest("GET", "/")));
154 
155         Assertions.assertEquals("myhttp://www.fancast.com:9999/full_episodes", extractor.generateKey(
156                 new HttpHost("myhttp", "www.fancast.com", 9999),
157                 new BasicHttpRequest("GET", "/full_episodes")));
158     }
159 
160     @Test
161     public void testEmptyPortEquivalentToDefaultPortForHttp() {
162         final HttpHost host1 = new HttpHost("foo.example.com:");
163         final HttpHost host2 = new HttpHost("foo.example.com:80");
164         final HttpRequest req = new BasicHttpRequest("GET", "/");
165         Assertions.assertEquals(extractor.generateKey(host1, req), extractor.generateKey(host2, req));
166     }
167 
168     @Test
169     public void testEmptyPortEquivalentToDefaultPortForHttps() {
170         final HttpHost host1 = new HttpHost("https", "foo.example.com", -1);
171         final HttpHost host2 = new HttpHost("https", "foo.example.com", 443);
172         final HttpRequest req = new BasicHttpRequest("GET", "/");
173         final String uri1 = extractor.generateKey(host1, req);
174         final String uri2 = extractor.generateKey(host2, req);
175         Assertions.assertEquals(uri1, uri2);
176     }
177 
178     @Test
179     public void testEmptyPortEquivalentToDefaultPortForHttpsAbsoluteURI() {
180         final HttpHost host = new HttpHost("https", "foo.example.com", -1);
181         final HttpGet get1 = new HttpGet("https://bar.example.com:/");
182         final HttpGet get2 = new HttpGet("https://bar.example.com:443/");
183         final String uri1 = extractor.generateKey(host, get1);
184         final String uri2 = extractor.generateKey(host, get2);
185         Assertions.assertEquals(uri1, uri2);
186     }
187 
188     @Test
189     public void testNotProvidedPortEquivalentToDefaultPortForHttpsAbsoluteURI() {
190         final HttpHost host = new HttpHost("https", "foo.example.com", -1);
191         final HttpGet get1 = new HttpGet("https://bar.example.com/");
192         final HttpGet get2 = new HttpGet("https://bar.example.com:443/");
193         final String uri1 = extractor.generateKey(host, get1);
194         final String uri2 = extractor.generateKey(host, get2);
195         Assertions.assertEquals(uri1, uri2);
196     }
197 
198     @Test
199     public void testNotProvidedPortEquivalentToDefaultPortForHttp() {
200         final HttpHost host1 = new HttpHost("foo.example.com");
201         final HttpHost host2 = new HttpHost("foo.example.com:80");
202         final HttpRequest req = new BasicHttpRequest("GET", "/");
203         Assertions.assertEquals(extractor.generateKey(host1, req), extractor.generateKey(host2, req));
204     }
205 
206     @Test
207     public void testHostNameComparisonsAreCaseInsensitive() {
208         final HttpHost host1 = new HttpHost("foo.example.com");
209         final HttpHost host2 = new HttpHost("FOO.EXAMPLE.COM");
210         final HttpRequest req = new BasicHttpRequest("GET", "/");
211         Assertions.assertEquals(extractor.generateKey(host1, req), extractor.generateKey(host2, req));
212     }
213 
214     @Test
215     public void testSchemeNameComparisonsAreCaseInsensitive() {
216         final HttpHost host1 = new HttpHost("http", "foo.example.com", -1);
217         final HttpHost host2 = new HttpHost("HTTP", "foo.example.com", -1);
218         final HttpRequest req = new BasicHttpRequest("GET", "/");
219         Assertions.assertEquals(extractor.generateKey(host1, req), extractor.generateKey(host2, req));
220     }
221 
222     @Test
223     public void testEmptyAbsPathIsEquivalentToSlash() {
224         final HttpHost host = new HttpHost("foo.example.com");
225         final HttpRequest req1 = new BasicHttpRequest("GET", "/");
226         final HttpRequest req2 = new HttpGet("http://foo.example.com");
227         Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
228     }
229 
230     @Test
231     public void testExtraDotSegmentsAreIgnored() {
232         final HttpHost host = new HttpHost("foo.example.com");
233         final HttpRequest req1 = new BasicHttpRequest("GET", "/");
234         final HttpRequest req2 = new HttpGet("http://foo.example.com/./");
235         Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
236     }
237 
238     @Test
239     public void testExtraDotDotSegmentsAreIgnored() {
240         final HttpHost host = new HttpHost("foo.example.com");
241         final HttpRequest req1 = new BasicHttpRequest("GET", "/");
242         final HttpRequest req2 = new HttpGet("http://foo.example.com/.././../");
243         Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
244     }
245 
246     @Test
247     public void testIntermidateDotDotSegementsAreEquivalent() {
248         final HttpHost host = new HttpHost("foo.example.com");
249         final HttpRequest req1 = new BasicHttpRequest("GET", "/home.html");
250         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/../home.html");
251         Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
252     }
253 
254     @Test
255     public void testIntermidateEncodedDotDotSegementsAreEquivalent() {
256         final HttpHost host = new HttpHost("foo.example.com");
257         final HttpRequest req1 = new BasicHttpRequest("GET", "/home.html");
258         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/../home.html");
259         Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
260     }
261 
262     @Test
263     public void testIntermidateDotSegementsAreEquivalent() {
264         final HttpHost host = new HttpHost("foo.example.com");
265         final HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home.html");
266         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/./home.html");
267         Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
268     }
269 
270     @Test
271     public void testEquivalentPathEncodingsAreEquivalent() {
272         final HttpHost host = new HttpHost("foo.example.com");
273         final HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home.html");
274         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/home.html");
275         Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
276     }
277 
278     @Test
279     public void testEquivalentExtraPathEncodingsAreEquivalent() {
280         final HttpHost host = new HttpHost("foo.example.com");
281         final HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home.html");
282         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/home.html");
283         Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
284     }
285 
286     @Test
287     public void testEquivalentExtraPathEncodingsWithPercentAreEquivalent() {
288         final HttpHost host = new HttpHost("foo.example.com");
289         final HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home%20folder.html");
290         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/home%20folder.html");
291         Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
292     }
293 
294     @Test
295     public void testGetURIWithQueryParameters() {
296         Assertions.assertEquals("http://www.comcast.net:80/?foo=bar", extractor.generateKey(
297                 new HttpHost("http", "www.comcast.net", -1), new BasicHttpRequest("GET", "/?foo=bar")));
298         Assertions.assertEquals("http://www.fancast.com:80/full_episodes?foo=bar", extractor.generateKey(
299                 new HttpHost("http", "www.fancast.com", -1), new BasicHttpRequest("GET",
300                         "/full_episodes?foo=bar")));
301     }
302 
303     private static Iterator<Header> headers(final Header... headers) {
304         return new BasicHeaderIterator(headers, null);
305     }
306 
307     @Test
308     public void testNormalizeHeaderElements() {
309         final List<String> tokens = new ArrayList<>();
310         CacheKeyGenerator.normalizeElements(headers(
311                 new BasicHeader("Accept-Encoding", "gzip,zip,deflate")
312         ), tokens::add);
313         Assertions.assertEquals(Arrays.asList("deflate", "gzip", "zip"), tokens);
314 
315         tokens.clear();
316         CacheKeyGenerator.normalizeElements(headers(
317                 new BasicHeader("Accept-Encoding", "  gZip  , Zip,  ,  ,  deflate  ")
318         ), tokens::add);
319         Assertions.assertEquals(Arrays.asList("deflate", "gzip", "zip"), tokens);
320 
321         tokens.clear();
322         CacheKeyGenerator.normalizeElements(headers(
323                 new BasicHeader("Accept-Encoding", "gZip,Zip,,"),
324                 new BasicHeader("Accept-Encoding", "   gZip,Zip,,,"),
325                 new BasicHeader("Accept-Encoding", "gZip,  ,,,deflate")
326         ), tokens::add);
327         Assertions.assertEquals(Arrays.asList("deflate", "gzip", "zip"), tokens);
328 
329         tokens.clear();
330         CacheKeyGenerator.normalizeElements(headers(
331                 new BasicHeader("Cookie", "name1 = value1 ;   p1 = v1 ; P2   = \"v2\""),
332                 new BasicHeader("Cookie", "name3;;;"),
333                 new BasicHeader("Cookie", "   name2 = \" value 2 \"   ; ; ; ,,,")
334         ), tokens::add);
335         Assertions.assertEquals(Arrays.asList("name1=value1;p1=v1;p2=v2", "name2=\" value 2 \"", "name3"), tokens);
336     }
337 
338     @Test
339     public void testGetVariantKey() {
340         final HttpRequest request = BasicRequestBuilder.get("/blah")
341                 .addHeader(HttpHeaders.USER_AGENT, "some-agent")
342                 .addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip,zip")
343                 .addHeader(HttpHeaders.ACCEPT_ENCODING, "deflate")
344                 .build();
345 
346         Assertions.assertEquals("{user-agent=some-agent}",
347                 extractor.generateVariantKey(request, Collections.singletonList(HttpHeaders.USER_AGENT)));
348         Assertions.assertEquals("{accept-encoding=deflate,gzip,zip}",
349                 extractor.generateVariantKey(request, Collections.singletonList(HttpHeaders.ACCEPT_ENCODING)));
350         Assertions.assertEquals("{accept-encoding=deflate,gzip,zip&user-agent=some-agent}",
351                 extractor.generateVariantKey(request, Arrays.asList(HttpHeaders.USER_AGENT, HttpHeaders.ACCEPT_ENCODING)));
352     }
353 
354     @Test
355     public void testGetVariantKeyInputNormalization() {
356         final HttpRequest request = BasicRequestBuilder.get("/blah")
357                 .addHeader(HttpHeaders.USER_AGENT, "Some-Agent")
358                 .addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip, ZIP,,")
359                 .addHeader(HttpHeaders.ACCEPT_ENCODING, "deflate")
360                 .build();
361 
362         Assertions.assertEquals("{user-agent=some-agent}",
363                 extractor.generateVariantKey(request, Collections.singletonList(HttpHeaders.USER_AGENT)));
364         Assertions.assertEquals("{accept-encoding=deflate,gzip,zip}",
365                 extractor.generateVariantKey(request, Collections.singletonList(HttpHeaders.ACCEPT_ENCODING)));
366         Assertions.assertEquals("{accept-encoding=deflate,gzip,zip&user-agent=some-agent}",
367                 extractor.generateVariantKey(request, Arrays.asList(HttpHeaders.USER_AGENT, HttpHeaders.ACCEPT_ENCODING)));
368         Assertions.assertEquals("{accept-encoding=deflate,gzip,zip&user-agent=some-agent}",
369                 extractor.generateVariantKey(request, Arrays.asList(HttpHeaders.USER_AGENT, HttpHeaders.ACCEPT_ENCODING, "USER-AGENT", HttpHeaders.ACCEPT_ENCODING)));
370     }
371 
372     @Test
373     public void testGetVariantKeyInputNormalizationReservedChars() {
374         final HttpRequest request = BasicRequestBuilder.get("/blah")
375                 .addHeader(HttpHeaders.USER_AGENT, "*===some-agent===*")
376                 .build();
377 
378         Assertions.assertEquals("{user-agent=%2A%3D%3D%3Dsome-agent%3D%3D%3D%2A}",
379                 extractor.generateVariantKey(request, Collections.singletonList(HttpHeaders.USER_AGENT)));
380     }
381 
382     @Test
383     public void testGetVariantKeyInputNoMatchingHeaders() {
384         final HttpRequest request = BasicRequestBuilder.get("/blah")
385                 .build();
386 
387         Assertions.assertEquals("{accept-encoding=&user-agent=}",
388                 extractor.generateVariantKey(request, Arrays.asList(HttpHeaders.ACCEPT_ENCODING, HttpHeaders.USER_AGENT)));
389     }
390 
391     @Test
392     public void testGetVariantKeyFromCachedResponse() {
393         final HttpRequest request = BasicRequestBuilder.get("/blah")
394                 .addHeader("User-Agent", "agent1")
395                 .addHeader("Accept-Encoding", "text/plain")
396                 .build();
397 
398         final HttpCacheEntry entry1 = HttpTestUtils.makeCacheEntry();
399         Assertions.assertNull(extractor.generateVariantKey(request, entry1));
400 
401         final HttpCacheEntry entry2 = HttpTestUtils.makeCacheEntry(
402                 new BasicHeader("Vary", "User-Agent, Accept-Encoding")
403         );
404         Assertions.assertEquals("{accept-encoding=text%2Fplain&user-agent=agent1}", extractor.generateVariantKey(request, entry2));
405     }
406 
407 }