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  public class TestConditionalRequestBuilder {
55  
56      private ConditionalRequestBuilder<HttpRequest> impl;
57      private HttpRequest request;
58  
59      @BeforeEach
60      public void setUp() throws Exception {
61          impl = new ConditionalRequestBuilder<>(request -> BasicRequestBuilder.copy(request).build());
62          request = new BasicHttpRequest("GET", "/");
63      }
64  
65      @Test
66      public 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      public void testConditionalRequestForEntryWithLastModifiedAndEtagIncludesBothAsValidators()
95              throws Exception {
96          final Instant now = Instant.now();
97          final Instant tenSecondsAgo = now.minusSeconds(10);
98          final Instant twentySecondsAgo = now.plusSeconds(20);
99          final String lmDate = DateUtils.formatStandardDate(twentySecondsAgo);
100         final String etag = "\"etag\"";
101         final Header[] headers = {
102             new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
103             new BasicHeader("Last-Modified", lmDate),
104             new BasicHeader("ETag", etag)
105         };
106         final HttpRequest basicRequest = new BasicHttpRequest("GET", "/");
107         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
108         final ResponseCacheControl cacheControl = ResponseCacheControl.builder().build();
109         final HttpRequest result = impl.buildConditionalRequest(cacheControl, basicRequest, cacheEntry);
110         Assertions.assertEquals(lmDate,
111                 result.getFirstHeader("If-Modified-Since").getValue());
112         Assertions.assertEquals(etag,
113                 result.getFirstHeader("If-None-Match").getValue());
114     }
115 
116     @Test
117     public void testBuildConditionalRequestWithETag() {
118         final String theMethod = "GET";
119         final String theUri = "/theuri";
120         final String theETag = "\"this is my eTag\"";
121 
122         final HttpRequest basicRequest = new BasicHttpRequest(theMethod, theUri);
123         basicRequest.addHeader("Accept-Encoding", "gzip");
124 
125         final Instant now = Instant.now();
126 
127         final Header[] headers = new Header[] {
128                 new BasicHeader("Date", DateUtils.formatStandardDate(now)),
129                 new BasicHeader("Last-Modified", DateUtils.formatStandardDate(now)),
130                 new BasicHeader("ETag", theETag) };
131 
132         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
133 
134         final ResponseCacheControl cacheControl = ResponseCacheControl.builder().build();
135         final HttpRequest newRequest = impl.buildConditionalRequest(cacheControl, basicRequest, cacheEntry);
136 
137         Assertions.assertEquals(theMethod, newRequest.getMethod());
138         Assertions.assertEquals(theUri, newRequest.getRequestUri());
139 
140         MatcherAssert.assertThat(
141                 newRequest.getHeaders(),
142                 HeadersMatcher.same(
143                         new BasicHeader("Accept-Encoding", "gzip"),
144                         new BasicHeader("If-None-Match", theETag),
145                         new BasicHeader("If-Modified-Since", DateUtils.formatStandardDate(now))));
146     }
147 
148     @Test
149     public void testCacheEntryWithMustRevalidateDoesEndToEndRevalidation() throws Exception {
150         final HttpRequest basicRequest = new BasicHttpRequest("GET","/");
151         final Instant now = Instant.now();
152         final Instant elevenSecondsAgo = now.minusSeconds(11);
153         final Instant tenSecondsAgo = now.minusSeconds(10);
154         final Instant nineSecondsAgo = now.plusSeconds(9);
155 
156         final Header[] cacheEntryHeaders = new Header[] {
157                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
158                 new BasicHeader("ETag", "\"etag\"")
159         };
160         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, cacheEntryHeaders);
161 
162         final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder()
163                 .setMaxAge(5)
164                 .setMustRevalidate(true)
165                 .build();
166         final HttpRequest result = impl.buildConditionalRequest(responseCacheControl, basicRequest, cacheEntry);
167 
168         final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(result);
169         Assertions.assertEquals(0, requestCacheControl.getMaxAge());
170     }
171 
172     @Test
173     public void testCacheEntryWithProxyRevalidateDoesEndToEndRevalidation() throws Exception {
174         final HttpRequest basicRequest = new BasicHttpRequest("GET", "/");
175         final Instant now = Instant.now();
176         final Instant elevenSecondsAgo = now.minusSeconds(11);
177         final Instant tenSecondsAgo = now.minusSeconds(10);
178         final Instant nineSecondsAgo = now.plusSeconds(9);
179 
180         final Header[] cacheEntryHeaders = new Header[] {
181                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
182                 new BasicHeader("ETag", "\"etag\"")
183         };
184         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, cacheEntryHeaders);
185 
186         final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder()
187                 .setMaxAge(5)
188                 .setProxyRevalidate(true)
189                 .build();
190         final HttpRequest result = impl.buildConditionalRequest(responseCacheControl, basicRequest, cacheEntry);
191 
192         final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(result);
193         Assertions.assertEquals(0, requestCacheControl.getMaxAge());
194     }
195 
196     @Test
197     public void testBuildUnconditionalRequestUsesGETMethod()
198         throws Exception {
199         final HttpRequest result = impl.buildUnconditionalRequest(request);
200         Assertions.assertEquals("GET", result.getMethod());
201     }
202 
203     @Test
204     public void testBuildUnconditionalRequestUsesRequestUri()
205         throws Exception {
206         final String uri = "/theURI";
207         request = new BasicHttpRequest("GET", uri);
208         final HttpRequest result = impl.buildUnconditionalRequest(request);
209         Assertions.assertEquals(uri, result.getRequestUri());
210     }
211 
212     @Test
213     public void testBuildUnconditionalRequestAddsCacheControlNoCache()
214         throws Exception {
215         final HttpRequest result = impl.buildUnconditionalRequest(request);
216         final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(result);
217         Assertions.assertTrue(requestCacheControl.isNoCache());
218     }
219 
220     @Test
221     public void testBuildUnconditionalRequestDoesNotUseIfRange()
222         throws Exception {
223         request.addHeader("If-Range","\"etag\"");
224         final HttpRequest result = impl.buildUnconditionalRequest(request);
225         Assertions.assertNull(result.getFirstHeader("If-Range"));
226     }
227 
228     @Test
229     public void testBuildUnconditionalRequestDoesNotUseIfMatch()
230         throws Exception {
231         request.addHeader("If-Match","\"etag\"");
232         final HttpRequest result = impl.buildUnconditionalRequest(request);
233         Assertions.assertNull(result.getFirstHeader("If-Match"));
234     }
235 
236     @Test
237     public void testBuildUnconditionalRequestDoesNotUseIfNoneMatch()
238         throws Exception {
239         request.addHeader("If-None-Match","\"etag\"");
240         final HttpRequest result = impl.buildUnconditionalRequest(request);
241         Assertions.assertNull(result.getFirstHeader("If-None-Match"));
242     }
243 
244     @Test
245     public void testBuildUnconditionalRequestDoesNotUseIfUnmodifiedSince()
246         throws Exception {
247         request.addHeader("If-Unmodified-Since", DateUtils.formatStandardDate(Instant.now()));
248         final HttpRequest result = impl.buildUnconditionalRequest(request);
249         Assertions.assertNull(result.getFirstHeader("If-Unmodified-Since"));
250     }
251 
252     @Test
253     public void testBuildUnconditionalRequestDoesNotUseIfModifiedSince()
254         throws Exception {
255         request.addHeader("If-Modified-Since", DateUtils.formatStandardDate(Instant.now()));
256         final HttpRequest result = impl.buildUnconditionalRequest(request);
257         Assertions.assertNull(result.getFirstHeader("If-Modified-Since"));
258     }
259 
260     @Test
261     public void testBuildUnconditionalRequestCarriesOtherRequestHeaders()
262         throws Exception {
263         request.addHeader("User-Agent","MyBrowser/1.0");
264         final HttpRequest result = impl.buildUnconditionalRequest(request);
265         Assertions.assertEquals("MyBrowser/1.0",
266                 result.getFirstHeader("User-Agent").getValue());
267     }
268 
269     @Test
270     public void testBuildConditionalRequestFromVariants() throws Exception {
271         final ETag etag1 = new ETag("123");
272         final ETag etag2 = new ETag("456");
273         final ETag etag3 = new ETag("789");
274 
275         final List<ETag> variantEntries = Arrays.asList(etag1, etag2, etag3);
276 
277         final HttpRequest conditional = impl.buildConditionalRequestFromVariants(request, variantEntries);
278 
279 
280         final Iterator<String> it = MessageSupport.iterateTokens(conditional, HttpHeaders.IF_NONE_MATCH);
281         final List<ETag> etags = new ArrayList<>();
282         while (it.hasNext()) {
283             etags.add(ETag.parse(it.next()));
284         }
285         MatcherAssert.assertThat(etags, Matchers.containsInAnyOrder(etag1, etag2, etag3));
286     }
287 
288 }