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.Arrays;
31  import java.util.List;
32  
33  import org.apache.hc.client5.http.cache.HttpCacheEntry;
34  import org.apache.hc.client5.http.utils.DateUtils;
35  import org.apache.hc.core5.http.Header;
36  import org.apache.hc.core5.http.HttpHeaders;
37  import org.apache.hc.core5.http.HttpRequest;
38  import org.apache.hc.core5.http.message.BasicHeader;
39  import org.apache.hc.core5.http.message.BasicHttpRequest;
40  import org.apache.hc.core5.http.support.BasicRequestBuilder;
41  import org.junit.jupiter.api.Assertions;
42  import org.junit.jupiter.api.BeforeEach;
43  import org.junit.jupiter.api.Test;
44  
45  public class TestConditionalRequestBuilder {
46  
47      private ConditionalRequestBuilder<HttpRequest> impl;
48      private HttpRequest request;
49  
50      @BeforeEach
51      public void setUp() throws Exception {
52          impl = new ConditionalRequestBuilder<>(request -> BasicRequestBuilder.copy(request).build());
53          request = new BasicHttpRequest("GET", "/");
54      }
55  
56      @Test
57      public void testBuildConditionalRequestWithLastModified() {
58          final String theMethod = "GET";
59          final String theUri = "/theuri";
60          final String lastModified = "this is my last modified date";
61  
62          final HttpRequest basicRequest = new BasicHttpRequest(theMethod, theUri);
63          basicRequest.addHeader("Accept-Encoding", "gzip");
64  
65          final Header[] headers = new Header[] {
66                  new BasicHeader("Date", DateUtils.formatStandardDate(Instant.now())),
67                  new BasicHeader("Last-Modified", lastModified) };
68  
69          final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
70          final ResponseCacheControl cacheControl = ResponseCacheControl.builder().build();
71          final HttpRequest newRequest = impl.buildConditionalRequest(cacheControl, basicRequest, cacheEntry);
72  
73          Assertions.assertEquals(theMethod, newRequest.getMethod());
74          Assertions.assertEquals(theUri, newRequest.getRequestUri());
75          Assertions.assertEquals(2, newRequest.getHeaders().length);
76  
77          Assertions.assertEquals("Accept-Encoding", newRequest.getHeaders()[0].getName());
78          Assertions.assertEquals("gzip", newRequest.getHeaders()[0].getValue());
79  
80          Assertions.assertEquals("If-Modified-Since", newRequest.getHeaders()[1].getName());
81          Assertions.assertEquals(lastModified, newRequest.getHeaders()[1].getValue());
82      }
83  
84      @Test
85      public void testConditionalRequestForEntryWithLastModifiedAndEtagIncludesBothAsValidators()
86              throws Exception {
87          final Instant now = Instant.now();
88          final Instant tenSecondsAgo = now.minusSeconds(10);
89          final Instant twentySecondsAgo = now.plusSeconds(20);
90          final String lmDate = DateUtils.formatStandardDate(twentySecondsAgo);
91          final String etag = "\"etag\"";
92          final Header[] headers = {
93              new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
94              new BasicHeader("Last-Modified", lmDate),
95              new BasicHeader("ETag", etag)
96          };
97          final HttpRequest basicRequest = new BasicHttpRequest("GET", "/");
98          final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
99          final ResponseCacheControl cacheControl = ResponseCacheControl.builder().build();
100         final HttpRequest result = impl.buildConditionalRequest(cacheControl, basicRequest, cacheEntry);
101         Assertions.assertEquals(lmDate,
102                 result.getFirstHeader("If-Modified-Since").getValue());
103         Assertions.assertEquals(etag,
104                 result.getFirstHeader("If-None-Match").getValue());
105     }
106 
107     @Test
108     public void testBuildConditionalRequestWithETag() {
109         final String theMethod = "GET";
110         final String theUri = "/theuri";
111         final String theETag = "this is my eTag";
112 
113         final HttpRequest basicRequest = new BasicHttpRequest(theMethod, theUri);
114         basicRequest.addHeader("Accept-Encoding", "gzip");
115 
116         final Header[] headers = new Header[] {
117                 new BasicHeader("Date", DateUtils.formatStandardDate(Instant.now())),
118                 new BasicHeader("Last-Modified", DateUtils.formatStandardDate(Instant.now())),
119                 new BasicHeader("ETag", theETag) };
120 
121         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
122 
123         final ResponseCacheControl cacheControl = ResponseCacheControl.builder().build();
124         final HttpRequest newRequest = impl.buildConditionalRequest(cacheControl, 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         };
150         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, cacheEntryHeaders);
151 
152         final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder()
153                 .setMaxAge(5)
154                 .setMustRevalidate(true)
155                 .build();
156         final HttpRequest result = impl.buildConditionalRequest(responseCacheControl, basicRequest, cacheEntry);
157 
158         final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(result);
159         Assertions.assertEquals(0, requestCacheControl.getMaxAge());
160     }
161 
162     @Test
163     public void testCacheEntryWithProxyRevalidateDoesEndToEndRevalidation() throws Exception {
164         final HttpRequest basicRequest = new BasicHttpRequest("GET", "/");
165         final Instant now = Instant.now();
166         final Instant elevenSecondsAgo = now.minusSeconds(11);
167         final Instant tenSecondsAgo = now.minusSeconds(10);
168         final Instant nineSecondsAgo = now.plusSeconds(9);
169 
170         final Header[] cacheEntryHeaders = new Header[] {
171                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
172                 new BasicHeader("ETag", "\"etag\"")
173         };
174         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, cacheEntryHeaders);
175 
176         final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder()
177                 .setMaxAge(5)
178                 .setProxyRevalidate(true)
179                 .build();
180         final HttpRequest result = impl.buildConditionalRequest(responseCacheControl, basicRequest, cacheEntry);
181 
182         final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(result);
183         Assertions.assertEquals(0, requestCacheControl.getMaxAge());
184     }
185 
186     @Test
187     public void testBuildUnconditionalRequestUsesGETMethod()
188         throws Exception {
189         final HttpRequest result = impl.buildUnconditionalRequest(request);
190         Assertions.assertEquals("GET", result.getMethod());
191     }
192 
193     @Test
194     public void testBuildUnconditionalRequestUsesRequestUri()
195         throws Exception {
196         final String uri = "/theURI";
197         request = new BasicHttpRequest("GET", uri);
198         final HttpRequest result = impl.buildUnconditionalRequest(request);
199         Assertions.assertEquals(uri, result.getRequestUri());
200     }
201 
202     @Test
203     public void testBuildUnconditionalRequestAddsCacheControlNoCache()
204         throws Exception {
205         final HttpRequest result = impl.buildUnconditionalRequest(request);
206         final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(result);
207         Assertions.assertTrue(requestCacheControl.isNoCache());
208     }
209 
210     @Test
211     public void testBuildUnconditionalRequestDoesNotUseIfRange()
212         throws Exception {
213         request.addHeader("If-Range","\"etag\"");
214         final HttpRequest result = impl.buildUnconditionalRequest(request);
215         Assertions.assertNull(result.getFirstHeader("If-Range"));
216     }
217 
218     @Test
219     public void testBuildUnconditionalRequestDoesNotUseIfMatch()
220         throws Exception {
221         request.addHeader("If-Match","\"etag\"");
222         final HttpRequest result = impl.buildUnconditionalRequest(request);
223         Assertions.assertNull(result.getFirstHeader("If-Match"));
224     }
225 
226     @Test
227     public void testBuildUnconditionalRequestDoesNotUseIfNoneMatch()
228         throws Exception {
229         request.addHeader("If-None-Match","\"etag\"");
230         final HttpRequest result = impl.buildUnconditionalRequest(request);
231         Assertions.assertNull(result.getFirstHeader("If-None-Match"));
232     }
233 
234     @Test
235     public void testBuildUnconditionalRequestDoesNotUseIfUnmodifiedSince()
236         throws Exception {
237         request.addHeader("If-Unmodified-Since", DateUtils.formatStandardDate(Instant.now()));
238         final HttpRequest result = impl.buildUnconditionalRequest(request);
239         Assertions.assertNull(result.getFirstHeader("If-Unmodified-Since"));
240     }
241 
242     @Test
243     public void testBuildUnconditionalRequestDoesNotUseIfModifiedSince()
244         throws Exception {
245         request.addHeader("If-Modified-Since", DateUtils.formatStandardDate(Instant.now()));
246         final HttpRequest result = impl.buildUnconditionalRequest(request);
247         Assertions.assertNull(result.getFirstHeader("If-Modified-Since"));
248     }
249 
250     @Test
251     public void testBuildUnconditionalRequestCarriesOtherRequestHeaders()
252         throws Exception {
253         request.addHeader("User-Agent","MyBrowser/1.0");
254         final HttpRequest result = impl.buildUnconditionalRequest(request);
255         Assertions.assertEquals("MyBrowser/1.0",
256                 result.getFirstHeader("User-Agent").getValue());
257     }
258 
259     @Test
260     public void testBuildConditionalRequestFromVariants() throws Exception {
261         final String etag1 = "\"123\"";
262         final String etag2 = "\"456\"";
263         final String etag3 = "\"789\"";
264 
265         final List<String> variantEntries = Arrays.asList(etag1, etag2, etag3);
266 
267         final HttpRequest conditional = impl.buildConditionalRequestFromVariants(request, variantEntries);
268 
269         // seems like a lot of work, but necessary, check for existence and exclusiveness
270         String ifNoneMatch = conditional.getFirstHeader(HttpHeaders.IF_NONE_MATCH).getValue();
271         Assertions.assertTrue(ifNoneMatch.contains(etag1));
272         Assertions.assertTrue(ifNoneMatch.contains(etag2));
273         Assertions.assertTrue(ifNoneMatch.contains(etag3));
274         ifNoneMatch = ifNoneMatch.replace(etag1, "");
275         ifNoneMatch = ifNoneMatch.replace(etag2, "");
276         ifNoneMatch = ifNoneMatch.replace(etag3, "");
277         ifNoneMatch = ifNoneMatch.replace(",","");
278         ifNoneMatch = ifNoneMatch.replace(" ", "");
279         Assertions.assertEquals(ifNoneMatch, "");
280     }
281 
282 }