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