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.time.Instant;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  import java.util.Map;
33  
34  import org.apache.hc.client5.http.cache.HeaderConstants;
35  import org.apache.hc.client5.http.cache.HttpCacheEntry;
36  import org.apache.hc.client5.http.utils.DateUtils;
37  import org.apache.hc.core5.http.Header;
38  import org.apache.hc.core5.http.HeaderElement;
39  import org.apache.hc.core5.http.HttpRequest;
40  import org.apache.hc.core5.http.message.BasicHeader;
41  import org.apache.hc.core5.http.message.BasicHttpRequest;
42  import org.apache.hc.core5.http.message.MessageSupport;
43  import org.apache.hc.core5.http.support.BasicRequestBuilder;
44  import org.junit.jupiter.api.Assertions;
45  import org.junit.jupiter.api.BeforeEach;
46  import org.junit.jupiter.api.Test;
47  
48  public class TestConditionalRequestBuilder {
49  
50      private ConditionalRequestBuilder<HttpRequest> impl;
51      private HttpRequest request;
52  
53      @BeforeEach
54      public void setUp() throws Exception {
55          impl = new ConditionalRequestBuilder<>(request -> BasicRequestBuilder.copy(request).build());
56          request = new BasicHttpRequest("GET", "/");
57      }
58  
59      @Test
60      public void testBuildConditionalRequestWithLastModified() {
61          final String theMethod = "GET";
62          final String theUri = "/theuri";
63          final String lastModified = "this is my last modified date";
64  
65          final HttpRequest basicRequest = new BasicHttpRequest(theMethod, theUri);
66          basicRequest.addHeader("Accept-Encoding", "gzip");
67  
68          final Header[] headers = new Header[] {
69                  new BasicHeader("Date", DateUtils.formatStandardDate(Instant.now())),
70                  new BasicHeader("Last-Modified", lastModified) };
71  
72          final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
73          final HttpRequest newRequest = impl.buildConditionalRequest(basicRequest, cacheEntry);
74  
75          Assertions.assertEquals(theMethod, newRequest.getMethod());
76          Assertions.assertEquals(theUri, newRequest.getRequestUri());
77          Assertions.assertEquals(2, newRequest.getHeaders().length);
78  
79          Assertions.assertEquals("Accept-Encoding", newRequest.getHeaders()[0].getName());
80          Assertions.assertEquals("gzip", newRequest.getHeaders()[0].getValue());
81  
82          Assertions.assertEquals("If-Modified-Since", newRequest.getHeaders()[1].getName());
83          Assertions.assertEquals(lastModified, newRequest.getHeaders()[1].getValue());
84      }
85  
86      @Test
87      public void testConditionalRequestForEntryWithLastModifiedAndEtagIncludesBothAsValidators()
88              throws Exception {
89          final Instant now = Instant.now();
90          final Instant tenSecondsAgo = now.minusSeconds(10);
91          final Instant twentySecondsAgo = now.plusSeconds(20);
92          final String lmDate = DateUtils.formatStandardDate(twentySecondsAgo);
93          final String etag = "\"etag\"";
94          final Header[] headers = {
95              new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
96              new BasicHeader("Last-Modified", lmDate),
97              new BasicHeader("ETag", etag)
98          };
99          final HttpRequest basicRequest = new BasicHttpRequest("GET", "/");
100         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
101         final HttpRequest result = impl.buildConditionalRequest(basicRequest, cacheEntry);
102         Assertions.assertEquals(lmDate,
103                 result.getFirstHeader("If-Modified-Since").getValue());
104         Assertions.assertEquals(etag,
105                 result.getFirstHeader("If-None-Match").getValue());
106     }
107 
108     @Test
109     public void testBuildConditionalRequestWithETag() {
110         final String theMethod = "GET";
111         final String theUri = "/theuri";
112         final String theETag = "this is my eTag";
113 
114         final HttpRequest basicRequest = new BasicHttpRequest(theMethod, theUri);
115         basicRequest.addHeader("Accept-Encoding", "gzip");
116 
117         final Header[] headers = new Header[] {
118                 new BasicHeader("Date", DateUtils.formatStandardDate(Instant.now())),
119                 new BasicHeader("Last-Modified", DateUtils.formatStandardDate(Instant.now())),
120                 new BasicHeader("ETag", theETag) };
121 
122         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
123 
124         final HttpRequest newRequest = impl.buildConditionalRequest(basicRequest, cacheEntry);
125 
126         Assertions.assertEquals(theMethod, newRequest.getMethod());
127         Assertions.assertEquals(theUri, newRequest.getRequestUri());
128 
129         Assertions.assertEquals(3, newRequest.getHeaders().length);
130 
131         Assertions.assertEquals("Accept-Encoding", newRequest.getHeaders()[0].getName());
132         Assertions.assertEquals("gzip", newRequest.getHeaders()[0].getValue());
133 
134         Assertions.assertEquals("If-None-Match", newRequest.getHeaders()[1].getName());
135         Assertions.assertEquals(theETag, newRequest.getHeaders()[1].getValue());
136     }
137 
138     @Test
139     public void testCacheEntryWithMustRevalidateDoesEndToEndRevalidation() throws Exception {
140         final HttpRequest basicRequest = new BasicHttpRequest("GET","/");
141         final Instant now = Instant.now();
142         final Instant elevenSecondsAgo = now.minusSeconds(11);
143         final Instant tenSecondsAgo = now.minusSeconds(10);
144         final Instant nineSecondsAgo = now.plusSeconds(9);
145 
146         final Header[] cacheEntryHeaders = new Header[] {
147                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
148                 new BasicHeader("ETag", "\"etag\""),
149                 new BasicHeader("Cache-Control","max-age=5, must-revalidate") };
150         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, cacheEntryHeaders);
151 
152         final HttpRequest result = impl.buildConditionalRequest(basicRequest, cacheEntry);
153 
154         boolean foundMaxAge0 = false;
155 
156         final Iterator<HeaderElement> it = MessageSupport.iterate(result, HeaderConstants.CACHE_CONTROL);
157         while (it.hasNext()) {
158             final HeaderElement elt = it.next();
159             if ("max-age".equalsIgnoreCase(elt.getName()) && "0".equals(elt.getValue())) {
160                 foundMaxAge0 = true;
161             }
162         }
163         Assertions.assertTrue(foundMaxAge0);
164     }
165 
166     @Test
167     public void testCacheEntryWithProxyRevalidateDoesEndToEndRevalidation() throws Exception {
168         final HttpRequest basicRequest = new BasicHttpRequest("GET", "/");
169         final Instant now = Instant.now();
170         final Instant elevenSecondsAgo = now.minusSeconds(11);
171         final Instant tenSecondsAgo = now.minusSeconds(10);
172         final Instant nineSecondsAgo = now.plusSeconds(9);
173 
174         final Header[] cacheEntryHeaders = new Header[] {
175                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
176                 new BasicHeader("ETag", "\"etag\""),
177                 new BasicHeader("Cache-Control","max-age=5, proxy-revalidate") };
178         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, cacheEntryHeaders);
179 
180         final HttpRequest result = impl.buildConditionalRequest(basicRequest, cacheEntry);
181 
182         boolean foundMaxAge0 = false;
183         final Iterator<HeaderElement> it = MessageSupport.iterate(result, HeaderConstants.CACHE_CONTROL);
184         while (it.hasNext()) {
185             final HeaderElement elt = it.next();
186             if ("max-age".equalsIgnoreCase(elt.getName()) && "0".equals(elt.getValue())) {
187                 foundMaxAge0 = true;
188             }
189         }
190         Assertions.assertTrue(foundMaxAge0);
191     }
192 
193     @Test
194     public void testBuildUnconditionalRequestUsesGETMethod()
195         throws Exception {
196         final HttpRequest result = impl.buildUnconditionalRequest(request);
197         Assertions.assertEquals("GET", result.getMethod());
198     }
199 
200     @Test
201     public void testBuildUnconditionalRequestUsesRequestUri()
202         throws Exception {
203         final String uri = "/theURI";
204         request = new BasicHttpRequest("GET", uri);
205         final HttpRequest result = impl.buildUnconditionalRequest(request);
206         Assertions.assertEquals(uri, result.getRequestUri());
207     }
208 
209     @Test
210     public void testBuildUnconditionalRequestAddsCacheControlNoCache()
211         throws Exception {
212         final HttpRequest result = impl.buildUnconditionalRequest(request);
213         boolean ccNoCacheFound = false;
214         final Iterator<HeaderElement> it = MessageSupport.iterate(result, HeaderConstants.CACHE_CONTROL);
215         while (it.hasNext()) {
216             final HeaderElement elt = it.next();
217             if ("no-cache".equals(elt.getName())) {
218                 ccNoCacheFound = true;
219             }
220         }
221         Assertions.assertTrue(ccNoCacheFound);
222     }
223 
224     @Test
225     public void testBuildUnconditionalRequestAddsPragmaNoCache()
226         throws Exception {
227         final HttpRequest result = impl.buildUnconditionalRequest(request);
228         boolean ccNoCacheFound = false;
229         final Iterator<HeaderElement> it = MessageSupport.iterate(result, HeaderConstants.PRAGMA);
230         while (it.hasNext()) {
231             final HeaderElement elt = it.next();
232             if ("no-cache".equals(elt.getName())) {
233                 ccNoCacheFound = true;
234             }
235         }
236         Assertions.assertTrue(ccNoCacheFound);
237     }
238 
239     @Test
240     public void testBuildUnconditionalRequestDoesNotUseIfRange()
241         throws Exception {
242         request.addHeader("If-Range","\"etag\"");
243         final HttpRequest result = impl.buildUnconditionalRequest(request);
244         Assertions.assertNull(result.getFirstHeader("If-Range"));
245     }
246 
247     @Test
248     public void testBuildUnconditionalRequestDoesNotUseIfMatch()
249         throws Exception {
250         request.addHeader("If-Match","\"etag\"");
251         final HttpRequest result = impl.buildUnconditionalRequest(request);
252         Assertions.assertNull(result.getFirstHeader("If-Match"));
253     }
254 
255     @Test
256     public void testBuildUnconditionalRequestDoesNotUseIfNoneMatch()
257         throws Exception {
258         request.addHeader("If-None-Match","\"etag\"");
259         final HttpRequest result = impl.buildUnconditionalRequest(request);
260         Assertions.assertNull(result.getFirstHeader("If-None-Match"));
261     }
262 
263     @Test
264     public void testBuildUnconditionalRequestDoesNotUseIfUnmodifiedSince()
265         throws Exception {
266         request.addHeader("If-Unmodified-Since", DateUtils.formatStandardDate(Instant.now()));
267         final HttpRequest result = impl.buildUnconditionalRequest(request);
268         Assertions.assertNull(result.getFirstHeader("If-Unmodified-Since"));
269     }
270 
271     @Test
272     public void testBuildUnconditionalRequestDoesNotUseIfModifiedSince()
273         throws Exception {
274         request.addHeader("If-Modified-Since", DateUtils.formatStandardDate(Instant.now()));
275         final HttpRequest result = impl.buildUnconditionalRequest(request);
276         Assertions.assertNull(result.getFirstHeader("If-Modified-Since"));
277     }
278 
279     @Test
280     public void testBuildUnconditionalRequestCarriesOtherRequestHeaders()
281         throws Exception {
282         request.addHeader("User-Agent","MyBrowser/1.0");
283         final HttpRequest result = impl.buildUnconditionalRequest(request);
284         Assertions.assertEquals("MyBrowser/1.0",
285                 result.getFirstHeader("User-Agent").getValue());
286     }
287 
288     @Test
289     public void testBuildConditionalRequestFromVariants() throws Exception {
290         final String etag1 = "\"123\"";
291         final String etag2 = "\"456\"";
292         final String etag3 = "\"789\"";
293 
294         final Map<String,Variant> variantEntries = new HashMap<>();
295         variantEntries.put(etag1, new Variant("A", HttpTestUtils.makeCacheEntry(new Header[] { new BasicHeader("ETag", etag1) })));
296         variantEntries.put(etag2, new Variant("B", HttpTestUtils.makeCacheEntry(new Header[] { new BasicHeader("ETag", etag2) })));
297         variantEntries.put(etag3, new Variant("C", HttpTestUtils.makeCacheEntry(new Header[] { new BasicHeader("ETag", etag3) })));
298 
299         final HttpRequest conditional = impl.buildConditionalRequestFromVariants(request, variantEntries);
300 
301         // seems like a lot of work, but necessary, check for existence and exclusiveness
302         String ifNoneMatch = conditional.getFirstHeader(HeaderConstants.IF_NONE_MATCH).getValue();
303         Assertions.assertTrue(ifNoneMatch.contains(etag1));
304         Assertions.assertTrue(ifNoneMatch.contains(etag2));
305         Assertions.assertTrue(ifNoneMatch.contains(etag3));
306         ifNoneMatch = ifNoneMatch.replace(etag1, "");
307         ifNoneMatch = ifNoneMatch.replace(etag2, "");
308         ifNoneMatch = ifNoneMatch.replace(etag3, "");
309         ifNoneMatch = ifNoneMatch.replace(",","");
310         ifNoneMatch = ifNoneMatch.replace(" ", "");
311         Assertions.assertEquals(ifNoneMatch, "");
312     }
313 
314 }