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