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 static org.junit.Assert.assertEquals;
30  import static org.junit.Assert.assertNotSame;
31  import static org.junit.Assert.assertThat;
32  import static org.junit.Assert.fail;
33  
34  import java.io.IOException;
35  import java.util.Date;
36  import java.util.HashMap;
37  import java.util.Map;
38  
39  import org.apache.hc.client5.http.cache.HttpCacheEntry;
40  import org.apache.hc.client5.http.utils.DateUtils;
41  import org.apache.hc.core5.http.Header;
42  import org.apache.hc.core5.http.HttpResponse;
43  import org.apache.hc.core5.http.HttpStatus;
44  import org.apache.hc.core5.http.message.BasicHeader;
45  import org.apache.hc.core5.http.message.BasicHttpResponse;
46  import org.junit.Before;
47  import org.junit.Test;
48  
49  public class TestCacheUpdateHandler {
50  
51      private Date requestDate;
52      private Date responseDate;
53  
54      private CacheUpdateHandler impl;
55      private HttpCacheEntry entry;
56      private Date now;
57      private Date oneSecondAgo;
58      private Date twoSecondsAgo;
59      private Date eightSecondsAgo;
60      private Date tenSecondsAgo;
61      private HttpResponse response;
62  
63      @Before
64      public void setUp() throws Exception {
65          requestDate = new Date(System.currentTimeMillis() - 1000);
66          responseDate = new Date();
67  
68          now = new Date();
69          oneSecondAgo = new Date(now.getTime() - 1000L);
70          twoSecondsAgo = new Date(now.getTime() - 2000L);
71          eightSecondsAgo = new Date(now.getTime() - 8000L);
72          tenSecondsAgo = new Date(now.getTime() - 10000L);
73  
74          response = new BasicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
75  
76          impl = new CacheUpdateHandler();
77      }
78  
79      @Test
80      public void testUpdateCacheEntryReturnsDifferentEntryInstance()
81              throws IOException {
82          entry = HttpTestUtils.makeCacheEntry();
83          final HttpCacheEntry newEntry = impl.updateCacheEntry(null, entry,
84                  requestDate, responseDate, response);
85          assertNotSame(newEntry, entry);
86      }
87  
88      @Test
89      public void testHeadersAreMergedCorrectly() throws IOException {
90          final Header[] headers = {
91                  new BasicHeader("Date", DateUtils.formatDate(responseDate)),
92                  new BasicHeader("ETag", "\"etag\"")};
93          entry = HttpTestUtils.makeCacheEntry(headers);
94          response.setHeaders();
95  
96          final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
97                  new Date(), new Date(), response);
98  
99          assertThat(updatedEntry, ContainsHeaderMatcher.contains("Date", DateUtils.formatDate(responseDate)));
100         assertThat(updatedEntry, ContainsHeaderMatcher.contains("ETag", "\"etag\""));
101     }
102 
103     @Test
104     public void testNewerHeadersReplaceExistingHeaders() throws IOException {
105         final Header[] headers = {
106                 new BasicHeader("Date", DateUtils.formatDate(requestDate)),
107                 new BasicHeader("Cache-Control", "private"),
108                 new BasicHeader("ETag", "\"etag\""),
109                 new BasicHeader("Last-Modified", DateUtils.formatDate(requestDate)),
110                 new BasicHeader("Cache-Control", "max-age=0"),};
111         entry = HttpTestUtils.makeCacheEntry(headers);
112 
113         response.setHeaders(new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)),
114                 new BasicHeader("Cache-Control", "public"));
115 
116         final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
117                 new Date(), new Date(), response);
118 
119         assertThat(updatedEntry, ContainsHeaderMatcher.contains("Date", DateUtils.formatDate(requestDate)));
120         assertThat(updatedEntry, ContainsHeaderMatcher.contains("ETag", "\"etag\""));
121         assertThat(updatedEntry, ContainsHeaderMatcher.contains("Last-Modified", DateUtils.formatDate(responseDate)));
122         assertThat(updatedEntry, ContainsHeaderMatcher.contains("Cache-Control", "public"));
123     }
124 
125     @Test
126     public void testNewHeadersAreAddedByMerge() throws IOException {
127 
128         final Header[] headers = {
129                 new BasicHeader("Date", DateUtils.formatDate(requestDate)),
130                 new BasicHeader("ETag", "\"etag\"")};
131 
132         entry = HttpTestUtils.makeCacheEntry(headers);
133         response.setHeaders(new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)),
134                 new BasicHeader("Cache-Control", "public"));
135 
136         final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
137                 new Date(), new Date(), response);
138 
139         assertThat(updatedEntry, ContainsHeaderMatcher.contains("Date", DateUtils.formatDate(requestDate)));
140         assertThat(updatedEntry, ContainsHeaderMatcher.contains("ETag", "\"etag\""));
141         assertThat(updatedEntry, ContainsHeaderMatcher.contains("Last-Modified", DateUtils.formatDate(responseDate)));
142         assertThat(updatedEntry, ContainsHeaderMatcher.contains("Cache-Control", "public"));
143     }
144 
145     @Test
146     public void oldHeadersRetainedIfResponseOlderThanEntry()
147             throws Exception {
148         final Header[] headers = {
149                 new BasicHeader("Date", DateUtils.formatDate(oneSecondAgo)),
150                 new BasicHeader("ETag", "\"new-etag\"")
151         };
152         entry = HttpTestUtils.makeCacheEntry(twoSecondsAgo, now, headers);
153         response.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
154         response.setHeader("ETag", "\"old-etag\"");
155         final HttpCacheEntry result = impl.updateCacheEntry("A", entry, new Date(),
156                 new Date(), response);
157         assertThat(result, ContainsHeaderMatcher.contains("Date", DateUtils.formatDate(oneSecondAgo)));
158         assertThat(result, ContainsHeaderMatcher.contains("ETag", "\"new-etag\""));
159     }
160 
161     @Test
162     public void testUpdatedEntryHasLatestRequestAndResponseDates()
163             throws IOException {
164         entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo);
165         final HttpCacheEntry updated = impl.updateCacheEntry(null, entry,
166                 twoSecondsAgo, oneSecondAgo, response);
167 
168         assertEquals(twoSecondsAgo, updated.getRequestDate());
169         assertEquals(oneSecondAgo, updated.getResponseDate());
170     }
171 
172     @Test
173     public void entry1xxWarningsAreRemovedOnUpdate() throws Exception {
174         final Header[] headers = {
175                 new BasicHeader("Warning", "110 fred \"Response is stale\""),
176                 new BasicHeader("ETag", "\"old\""),
177                 new BasicHeader("Date", DateUtils.formatDate(eightSecondsAgo))
178         };
179         entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, headers);
180         response.setHeader("ETag", "\"new\"");
181         response.setHeader("Date", DateUtils.formatDate(twoSecondsAgo));
182         final HttpCacheEntry updated = impl.updateCacheEntry(null, entry,
183                 twoSecondsAgo, oneSecondAgo, response);
184 
185         assertEquals(0, updated.getHeaders("Warning").length);
186     }
187 
188     @Test
189     public void entryWithMalformedDateIsStillUpdated() throws Exception {
190         final Header[] headers = {
191                 new BasicHeader("ETag", "\"old\""),
192                 new BasicHeader("Date", "bad-date")
193         };
194         entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, headers);
195         response.setHeader("ETag", "\"new\"");
196         response.setHeader("Date", DateUtils.formatDate(twoSecondsAgo));
197         final HttpCacheEntry updated = impl.updateCacheEntry(null, entry,
198                 twoSecondsAgo, oneSecondAgo, response);
199 
200         assertEquals("\"new\"", updated.getFirstHeader("ETag").getValue());
201     }
202 
203     @Test
204     public void entryIsStillUpdatedByResponseWithMalformedDate() throws Exception {
205         final Header[] headers = {
206                 new BasicHeader("ETag", "\"old\""),
207                 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo))
208         };
209         entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, headers);
210         response.setHeader("ETag", "\"new\"");
211         response.setHeader("Date", "bad-date");
212         final HttpCacheEntry updated = impl.updateCacheEntry(null, entry,
213                 twoSecondsAgo, oneSecondAgo, response);
214 
215         assertEquals("\"new\"", updated.getFirstHeader("ETag").getValue());
216     }
217 
218     @Test
219     public void cannotUpdateFromANon304OriginResponse() throws Exception {
220         entry = HttpTestUtils.makeCacheEntry();
221         response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
222         try {
223             impl.updateCacheEntry("A", entry, new Date(), new Date(),
224                     response);
225             fail("should have thrown exception");
226         } catch (final IllegalArgumentException expected) {
227         }
228     }
229 
230     @Test
231     public void testCacheUpdateAddsVariantURIToParentEntry() throws Exception {
232         final String parentCacheKey = "parentCacheKey";
233         final String variantCacheKey = "variantCacheKey";
234         final String existingVariantKey = "existingVariantKey";
235         final String newVariantCacheKey = "newVariantCacheKey";
236         final String newVariantKey = "newVariantKey";
237         final Map<String,String> existingVariants = new HashMap<>();
238         existingVariants.put(existingVariantKey, variantCacheKey);
239         final HttpCacheEntry parent = HttpTestUtils.makeCacheEntry(existingVariants);
240         final HttpCacheEntry variant = HttpTestUtils.makeCacheEntry();
241 
242         final HttpCacheEntry result = impl.updateParentCacheEntry(parentCacheKey, parent, variant, newVariantKey, newVariantCacheKey);
243         final Map<String,String> resultMap = result.getVariantMap();
244         assertEquals(2, resultMap.size());
245         assertEquals(variantCacheKey, resultMap.get(existingVariantKey));
246         assertEquals(newVariantCacheKey, resultMap.get(newVariantKey));
247     }
248 
249     @Test
250     public void testContentEncodingHeaderIsNotUpdatedByMerge() throws IOException {
251         final Header[] headers = {
252                 new BasicHeader("Date", DateUtils.formatDate(requestDate)),
253                 new BasicHeader("ETag", "\"etag\""),
254                 new BasicHeader("Content-Encoding", "identity")};
255 
256         entry = HttpTestUtils.makeCacheEntry(headers);
257         response.setHeaders(new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)),
258                 new BasicHeader("Cache-Control", "public"),
259                 new BasicHeader("Content-Encoding", "gzip"));
260 
261         final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
262                 new Date(), new Date(), response);
263 
264         final Header[] updatedHeaders = updatedEntry.getHeaders();
265         headersContain(updatedHeaders, "Content-Encoding", "identity");
266         headersNotContain(updatedHeaders, "Content-Encoding", "gzip");
267     }
268 
269     @Test
270     public void testContentLengthIsNotAddedWhenTransferEncodingIsPresent() throws IOException {
271         final Header[] headers = {
272                 new BasicHeader("Date", DateUtils.formatDate(requestDate)),
273                 new BasicHeader("ETag", "\"etag\""),
274                 new BasicHeader("Transfer-Encoding", "chunked")};
275 
276         entry = HttpTestUtils.makeCacheEntry(headers);
277         response.setHeaders(new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)),
278                 new BasicHeader("Cache-Control", "public"),
279                 new BasicHeader("Content-Length", "0"));
280 
281         final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
282                 new Date(), new Date(), response);
283 
284         final Header[] updatedHeaders = updatedEntry.getHeaders();
285         headersContain(updatedHeaders, "Transfer-Encoding", "chunked");
286         headersNotContain(updatedHeaders, "Content-Length", "0");
287     }
288 
289     private void headersContain(final Header[] headers, final String name, final String value) {
290         for (final Header header : headers) {
291             if (header.getName().equals(name)) {
292                 if (header.getValue().equals(value)) {
293                     return;
294                 }
295             }
296         }
297         fail("Header [" + name + ": " + value + "] not found in headers.");
298     }
299 
300     private void headersNotContain(final Header[] headers, final String name, final String value) {
301         for (final Header header : headers) {
302             if (header.getName().equals(name)) {
303                 if (header.getValue().equals(value)) {
304                     fail("Header [" + name + ": " + value + "] found in headers where it should not be");
305                 }
306             }
307         }
308     }
309 }