1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.client5.http.impl.cache;
29
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.net.URL;
33 import java.nio.charset.StandardCharsets;
34 import java.time.Instant;
35 import java.util.HashSet;
36 import java.util.Set;
37
38 import org.apache.hc.client5.http.cache.HttpCacheEntry;
39 import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer;
40 import org.apache.hc.client5.http.cache.HttpCacheStorageEntry;
41 import org.apache.hc.client5.http.cache.ResourceIOException;
42 import org.apache.hc.core5.http.ContentType;
43 import org.apache.hc.core5.http.HttpHeaders;
44 import org.apache.hc.core5.http.HttpStatus;
45 import org.apache.hc.core5.http.message.BasicHeader;
46 import org.apache.hc.core5.util.ByteArrayBuffer;
47 import org.hamcrest.MatcherAssert;
48 import org.hamcrest.Matchers;
49 import org.junit.jupiter.api.Assertions;
50 import org.junit.jupiter.api.BeforeEach;
51 import org.junit.jupiter.api.Test;
52
53 class TestHttpByteArrayCacheEntrySerializer {
54
55 private HttpCacheEntrySerializer<byte[]> httpCacheEntrySerializer;
56
57 @BeforeEach
58 void before() {
59 httpCacheEntrySerializer = HttpByteArrayCacheEntrySerializer.INSTANCE;
60 }
61
62 @Test
63 void testSimpleSerializeAndDeserialize() throws Exception {
64 final String content = "Hello World";
65 final ContentType contentType = ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8);
66 final HttpCacheEntry cacheEntry = new HttpCacheEntry(Instant.now(), Instant.now(),
67 "GET", "/stuff", HttpTestUtils.headers(),
68 HttpStatus.SC_OK, HttpTestUtils.headers(new BasicHeader(HttpHeaders.CONTENT_TYPE, contentType.toString())),
69 new HeapResource(content.getBytes(contentType.getCharset())),
70 null);
71 final HttpCacheStorageEntry storageEntry = new HttpCacheStorageEntry("unique-cache-key", cacheEntry);
72 final byte[] serialized = httpCacheEntrySerializer.serialize(storageEntry);
73
74 final HttpCacheStorageEntry deserialized = httpCacheEntrySerializer.deserialize(serialized);
75 MatcherAssert.assertThat(deserialized.getKey(), Matchers.equalTo(storageEntry.getKey()));
76 MatcherAssert.assertThat(deserialized.getContent(), HttpCacheEntryMatcher.equivalent(storageEntry.getContent()));
77 }
78
79 @Test
80 void testSerializeAndDeserializeLargeContent() throws Exception {
81 final ContentType contentType = ContentType.IMAGE_JPEG;
82 final HeapResource resource = load(getClass().getResource("/ApacheLogo.png"));
83 final HttpCacheEntry cacheEntry = new HttpCacheEntry(Instant.now(), Instant.now(),
84 "GET", "/stuff", HttpTestUtils.headers(),
85 HttpStatus.SC_OK, HttpTestUtils.headers(new BasicHeader(HttpHeaders.CONTENT_TYPE, contentType.toString())),
86 resource,
87 null);
88 final HttpCacheStorageEntry storageEntry = new HttpCacheStorageEntry("unique-cache-key", cacheEntry);
89 final byte[] serialized = httpCacheEntrySerializer.serialize(storageEntry);
90
91 final HttpCacheStorageEntry deserialized = httpCacheEntrySerializer.deserialize(serialized);
92 MatcherAssert.assertThat(deserialized.getKey(), Matchers.equalTo(storageEntry.getKey()));
93 MatcherAssert.assertThat(deserialized.getContent(), HttpCacheEntryMatcher.equivalent(storageEntry.getContent()));
94 }
95
96
97
98
99 @Test
100 void testInvalidCacheEntry() throws Exception {
101
102 final HeapResource resource = load(getClass().getResource("/ApacheLogo.png"));
103 Assertions.assertThrows(ResourceIOException.class, () ->
104 httpCacheEntrySerializer.deserialize(resource.get()));
105 }
106
107
108
109
110 @Test
111 void testTruncatedCacheEntry() {
112 final String content1 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
113 "HC-Key: unique-cache-key\n" +
114 "HC-Resource-Length: 11\n" +
115 "HC-Request-Instant: 1686210849596\n" +
116 "HC-Response-Instant: 1686210849596\n" +
117 "\n" +
118 "GET /stuff HTTP/1.1\n" +
119 "\n" +
120 "HTTP/1.1 200 \n" +
121 "Content-Type: text/plain; charset=UTF-8\n" +
122 "Cache-control: public, max-age=31536000\n" +
123 "\n" +
124 "Huh?";
125 final byte[] bytes1 = content1.getBytes(StandardCharsets.UTF_8);
126 final ResourceIOException exception1 = Assertions.assertThrows(ResourceIOException.class, () ->
127 httpCacheEntrySerializer.deserialize(bytes1));
128 Assertions.assertEquals("Unexpected end of cache content", exception1.getMessage());
129
130 final String content2 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
131 "HC-Key: unique-cache-key\n" +
132 "HC-Resource-Length: 11\n" +
133 "HC-Request-Instant: 1686210849596\n" +
134 "HC-Response-Instant: 1686210849596\n" +
135 "\n" +
136 "GET /stuff HTTP/1.1\n" +
137 "\n" +
138 "HTTP/1.1 200 \n" +
139 "Content-Type: text/plain; charset=UTF-8\n" +
140 "Cache-control: public, max-age=31536000\n";
141 final byte[] bytes2 = content2.getBytes(StandardCharsets.UTF_8);
142 final ResourceIOException exception2 = Assertions.assertThrows(ResourceIOException.class, () ->
143 httpCacheEntrySerializer.deserialize(bytes2));
144 Assertions.assertEquals("Unexpected end of stream", exception2.getMessage());
145
146 final String content3 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
147 "HC-Key: unique-cache-key\n" +
148 "HC-Resource-Length: 11\n" +
149 "HC-Request-Instant: 1686210849596\n" +
150 "HC-Response-Instant: 1686210849596\n" +
151 "\n" +
152 "GET /stuff HTTP/1.1\n" +
153 "\n";
154 final byte[] bytes3 = content3.getBytes(StandardCharsets.UTF_8);
155 final ResourceIOException exception3 = Assertions.assertThrows(ResourceIOException.class, () ->
156 httpCacheEntrySerializer.deserialize(bytes3));
157 Assertions.assertEquals("Unexpected end of stream", exception3.getMessage());
158
159 final String content4 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
160 "HC-Key: unique-cache-key\n" +
161 "HC-Resource-Length: 11\n" +
162 "HC-Request-Instant: 1686210849596\n" +
163 "HC-Response-Instant: 1686210849596\n";
164 final byte[] bytes4 = content4.getBytes(StandardCharsets.UTF_8);
165 final ResourceIOException exception4 = Assertions.assertThrows(ResourceIOException.class, () ->
166 httpCacheEntrySerializer.deserialize(bytes4));
167 Assertions.assertEquals("Unexpected end of stream", exception4.getMessage());
168
169 final String content5 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
170 "HC-Key: unique-cache-key\n";
171 final byte[] bytes5 = content5.getBytes(StandardCharsets.UTF_8);
172 final ResourceIOException exception5 = Assertions.assertThrows(ResourceIOException.class, () ->
173 httpCacheEntrySerializer.deserialize(bytes5));
174 Assertions.assertEquals("Unexpected end of stream", exception5.getMessage());
175
176 final String content6 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n";
177 final byte[] bytes6 = content6.getBytes(StandardCharsets.UTF_8);
178 final ResourceIOException exception6 = Assertions.assertThrows(ResourceIOException.class, () ->
179 httpCacheEntrySerializer.deserialize(bytes6));
180 Assertions.assertEquals("Unexpected end of stream", exception6.getMessage());
181
182 final String content7 = "HttpClient CacheEntry 1\n";
183 final byte[] bytes7 = content7.getBytes(StandardCharsets.UTF_8);
184 final ResourceIOException exception7 = Assertions.assertThrows(ResourceIOException.class, () ->
185 httpCacheEntrySerializer.deserialize(bytes7));
186 Assertions.assertEquals("Unexpected cache entry version line", exception7.getMessage());
187 }
188
189
190
191
192 @Test
193 void testMissingHeaderCacheEntry() {
194 final String content1 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
195 "HC-Key: unique-cache-key\n" +
196 "HC-Resource-Length: 11\n" +
197 "HC-Response-Instant: 1686210849596\n" +
198 "\n" +
199 "GET /stuff HTTP/1.1\n" +
200 "\n" +
201 "HTTP/1.1 200 \n" +
202 "Content-Type: text/plain; charset=UTF-8\n" +
203 "Cache-control: public, max-age=31536000\n" +
204 "\n" +
205 "Hello World";
206 final byte[] bytes1 = content1.getBytes(StandardCharsets.UTF_8);
207 final ResourceIOException exception1 = Assertions.assertThrows(ResourceIOException.class, () ->
208 httpCacheEntrySerializer.deserialize(bytes1));
209 Assertions.assertEquals("Invalid cache header format", exception1.getMessage());
210
211 final String content2 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
212 "HC-Key: unique-cache-key\n" +
213 "HC-Resource-Length: 11\n" +
214 "HC-Request-Instant: 1686210849596\n" +
215 "\n" +
216 "GET /stuff HTTP/1.1\n" +
217 "\n" +
218 "HTTP/1.1 200 \n" +
219 "Content-Type: text/plain; charset=UTF-8\n" +
220 "Cache-control: public, max-age=31536000\n" +
221 "\n" +
222 "Hello World";
223 final byte[] bytes2 = content2.getBytes(StandardCharsets.UTF_8);
224 final ResourceIOException exception2 = Assertions.assertThrows(ResourceIOException.class, () ->
225 httpCacheEntrySerializer.deserialize(bytes2));
226 Assertions.assertEquals("Invalid cache header format", exception2.getMessage());
227 }
228
229
230
231
232 @Test
233 void testInvalidHeaderCacheEntry() {
234 final String content1 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
235 "HC-Key: unique-cache-key\n" +
236 "HC-Resource-Length: 11\n" +
237 "HC-Request-Instant: boom\n" +
238 "HC-Response-Instant: 1686210849596\n" +
239 "\n" +
240 "GET /stuff HTTP/1.1\n" +
241 "\n" +
242 "HTTP/1.1 200 \n" +
243 "Content-Type: text/plain; charset=UTF-8\n" +
244 "Cache-control: public, max-age=31536000\n" +
245 "\n" +
246 "Hello World";
247 final byte[] bytes1 = content1.getBytes(StandardCharsets.UTF_8);
248 final ResourceIOException exception1 = Assertions.assertThrows(ResourceIOException.class, () ->
249 httpCacheEntrySerializer.deserialize(bytes1));
250 Assertions.assertEquals("Invalid cache header format", exception1.getMessage());
251 final String content2 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
252 "HC-Key: unique-cache-key\n" +
253 "HC-Resource-Length: 11\n" +
254 "HC-Request-Instant: 1686210849596\n" +
255 "HC-Response-Instant: boom\n" +
256 "\n" +
257 "GET /stuff HTTP/1.1\n" +
258 "\n" +
259 "HTTP/1.1 200 \n" +
260 "Content-Type: text/plain; charset=UTF-8\n" +
261 "Cache-control: public, max-age=31536000\n" +
262 "\n" +
263 "Hello World";
264 final byte[] bytes2 = content2.getBytes(StandardCharsets.UTF_8);
265 final ResourceIOException exception2 = Assertions.assertThrows(ResourceIOException.class, () ->
266 httpCacheEntrySerializer.deserialize(bytes2));
267 Assertions.assertEquals("Invalid cache header format", exception2.getMessage());
268 }
269
270
271
272
273 @Test
274 void testInvalidRequestLineCacheEntry() {
275 final String content1 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
276 "HC-Key: unique-cache-key\n" +
277 "HC-Resource-Length: 11\n" +
278 "HC-Request-Instant: 1686210849596\n" +
279 "HC-Response-Instant: 1686210849596\n" +
280 "\n" +
281 "GET boom\n" +
282 "\n" +
283 "HTTP/1.1 200 \n" +
284 "Content-Type: text/plain; charset=UTF-8\n" +
285 "Cache-control: public, max-age=31536000\n" +
286 "\n" +
287 "Hello World";
288 final byte[] bytes1 = content1.getBytes(StandardCharsets.UTF_8);
289 final ResourceIOException exception1 = Assertions.assertThrows(ResourceIOException.class, () ->
290 httpCacheEntrySerializer.deserialize(bytes1));
291 Assertions.assertEquals("Invalid cache header format", exception1.getMessage());
292 }
293
294
295
296
297 @Test
298 void testInvalidStatusLineCacheEntry() {
299 final String content1 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
300 "HC-Key: unique-cache-key\n" +
301 "HC-Resource-Length: 11\n" +
302 "HC-Request-Instant: 1686210849596\n" +
303 "HC-Response-Instant: 1686210849596\n" +
304 "\n" +
305 "GET /stuff HTTP/1.1\n" +
306 "\n" +
307 "HTTP/1.1 boom \n" +
308 "Content-Type: text/plain; charset=UTF-8\n" +
309 "Cache-control: public, max-age=31536000\n" +
310 "\n" +
311 "Hello World";
312 final byte[] bytes1 = content1.getBytes(StandardCharsets.UTF_8);
313 final ResourceIOException exception1 = Assertions.assertThrows(ResourceIOException.class, () ->
314 httpCacheEntrySerializer.deserialize(bytes1));
315 Assertions.assertEquals("Invalid cache header format", exception1.getMessage());
316 }
317
318
319
320
321 @Test
322 void noHeadersTest() throws Exception {
323 final String content = "Hello World";
324 final ContentType contentType = ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8);
325 final HttpCacheEntry cacheEntry = new HttpCacheEntry(Instant.now(), Instant.now(),
326 "GET", "/stuff", HttpTestUtils.headers(),
327 HttpStatus.SC_OK, HttpTestUtils.headers(),
328 new HeapResource(content.getBytes(contentType.getCharset())),
329 null);
330 final HttpCacheStorageEntry storageEntry = new HttpCacheStorageEntry("unique-cache-key", cacheEntry);
331 final byte[] serialized = httpCacheEntrySerializer.serialize(storageEntry);
332
333 final HttpCacheStorageEntry deserialized = httpCacheEntrySerializer.deserialize(serialized);
334 MatcherAssert.assertThat(deserialized.getKey(), Matchers.equalTo(storageEntry.getKey()));
335 MatcherAssert.assertThat(deserialized.getContent(), HttpCacheEntryMatcher.equivalent(storageEntry.getContent()));
336 }
337
338
339
340
341 @Test
342 void emptyBodyTest() throws Exception {
343 final HttpCacheEntry cacheEntry = new HttpCacheEntry(Instant.now(), Instant.now(),
344 "GET", "/stuff", HttpTestUtils.headers(),
345 HttpStatus.SC_OK, HttpTestUtils.headers(),
346 new HeapResource(new byte[] {}),
347 null);
348 final HttpCacheStorageEntry storageEntry = new HttpCacheStorageEntry("unique-cache-key", cacheEntry);
349 final byte[] serialized = httpCacheEntrySerializer.serialize(storageEntry);
350
351 final HttpCacheStorageEntry deserialized = httpCacheEntrySerializer.deserialize(serialized);
352 MatcherAssert.assertThat(deserialized.getKey(), Matchers.equalTo(storageEntry.getKey()));
353 MatcherAssert.assertThat(deserialized.getContent(), HttpCacheEntryMatcher.equivalent(storageEntry.getContent()));
354 }
355
356
357
358
359 @Test
360 void noBodyTest() throws Exception {
361 final HttpCacheEntry cacheEntry = new HttpCacheEntry(Instant.now(), Instant.now(),
362 "GET", "/stuff", HttpTestUtils.headers(),
363 HttpStatus.SC_OK, HttpTestUtils.headers(),
364 null,
365 null);
366 final HttpCacheStorageEntry storageEntry = new HttpCacheStorageEntry("unique-cache-key", cacheEntry);
367 final byte[] serialized = httpCacheEntrySerializer.serialize(storageEntry);
368
369 final HttpCacheStorageEntry deserialized = httpCacheEntrySerializer.deserialize(serialized);
370 MatcherAssert.assertThat(deserialized.getKey(), Matchers.equalTo(storageEntry.getKey()));
371 MatcherAssert.assertThat(deserialized.getContent(), HttpCacheEntryMatcher.equivalent(storageEntry.getContent()));
372 }
373
374
375
376
377 @Test
378 void testSimpleVariantMap() throws Exception {
379 final String content = "Hello World";
380 final ContentType contentType = ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8);
381 final Set<String> variants = new HashSet<>();
382 variants.add("{Accept-Encoding=gzip}");
383 variants.add("{Accept-Encoding=compress}");
384 final HttpCacheEntry cacheEntry = new HttpCacheEntry(Instant.now(), Instant.now(),
385 "GET", "/stuff", HttpTestUtils.headers(),
386 HttpStatus.SC_OK, HttpTestUtils.headers(new BasicHeader(HttpHeaders.CONTENT_TYPE, contentType.toString())),
387 new HeapResource(content.getBytes(contentType.getCharset())),
388 variants);
389 final HttpCacheStorageEntry storageEntry = new HttpCacheStorageEntry("unique-cache-key", cacheEntry);
390 final byte[] serialized = httpCacheEntrySerializer.serialize(storageEntry);
391
392 final HttpCacheStorageEntry deserialized = httpCacheEntrySerializer.deserialize(serialized);
393 MatcherAssert.assertThat(deserialized.getKey(), Matchers.equalTo(storageEntry.getKey()));
394 MatcherAssert.assertThat(deserialized.getContent(), HttpCacheEntryMatcher.equivalent(storageEntry.getContent()));
395 }
396
397
398
399
400 @Test
401 void testDeserializeCacheEntryWithTrailingGarbage() {
402 final String content1 =HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
403 "HC-Key: unique-cache-key\n" +
404 "HC-Resource-Length: 11\n" +
405 "HC-Request-Instant: 1686210849596\n" +
406 "HC-Response-Instant: 1686210849596\n" +
407 "\n" +
408 "GET /stuff HTTP/1.1\n" +
409 "\n" +
410 "HTTP/1.1 200 \n" +
411 "Content-Type: text/plain; charset=UTF-8\n" +
412 "Cache-control: public, max-age=31536000\n" +
413 "\n" +
414 "Hello World..... Rubbish";
415 final byte[] bytes1 = content1.getBytes(StandardCharsets.UTF_8);
416 final ResourceIOException exception1 = Assertions.assertThrows(ResourceIOException.class, () ->
417 httpCacheEntrySerializer.deserialize(bytes1));
418 Assertions.assertEquals("Unexpected content at the end of cache content", exception1.getMessage());
419
420 final String content2 =HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
421 "HC-Key: unique-cache-key\n" +
422 "HC-Request-Instant: 1686210849596\n" +
423 "HC-Response-Instant: 1686210849596\n" +
424 "\n" +
425 "GET /stuff HTTP/1.1\n" +
426 "\n" +
427 "HTTP/1.1 200 \n" +
428 "Content-Type: text/plain; charset=UTF-8\n" +
429 "Cache-control: public, max-age=31536000\n" +
430 "\n" +
431 "Rubbish";
432 final byte[] bytes2 = content2.getBytes(StandardCharsets.UTF_8);
433 final ResourceIOException exception2 = Assertions.assertThrows(ResourceIOException.class, () ->
434 httpCacheEntrySerializer.deserialize(bytes2));
435 Assertions.assertEquals("Unexpected content at the end of cache content", exception2.getMessage());
436 }
437
438 static HeapResource load(final URL resource) throws IOException {
439 try (final InputStream in = resource.openStream()) {
440 final ByteArrayBuffer buf = new ByteArrayBuffer(1024);
441 final byte[] tmp = new byte[2048];
442 int len;
443 while ((len = in.read(tmp)) != -1) {
444 buf.append(tmp, 0, len);
445 }
446 return new HeapResource(buf.toByteArray());
447 }
448 }
449
450 }