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 package org.apache.hc.client5.http.impl.cache;
28
29 import java.time.Instant;
30 import java.util.HashMap;
31 import java.util.Map;
32
33 import org.apache.hc.client5.http.cache.HeaderConstants;
34 import org.apache.hc.client5.http.cache.HttpCacheEntry;
35 import org.apache.hc.client5.http.cache.HttpCacheInvalidator;
36 import org.apache.hc.client5.http.cache.HttpCacheStorage;
37 import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
38 import org.apache.hc.client5.http.cache.ResourceFactory;
39 import org.apache.hc.client5.http.cache.ResourceIOException;
40 import org.apache.hc.core5.http.Header;
41 import org.apache.hc.core5.http.HttpHost;
42 import org.apache.hc.core5.http.HttpRequest;
43 import org.apache.hc.core5.http.HttpResponse;
44 import org.apache.hc.core5.http.Method;
45 import org.apache.hc.core5.http.message.RequestLine;
46 import org.apache.hc.core5.http.message.StatusLine;
47 import org.apache.hc.core5.util.ByteArrayBuffer;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 class BasicHttpCache implements HttpCache {
52
53 private static final Logger LOG = LoggerFactory.getLogger(BasicHttpCache.class);
54
55 private final CacheUpdateHandler cacheUpdateHandler;
56 private final CacheKeyGenerator cacheKeyGenerator;
57 private final HttpCacheInvalidator cacheInvalidator;
58 private final HttpCacheStorage storage;
59
60 public BasicHttpCache(
61 final ResourceFactory resourceFactory,
62 final HttpCacheStorage storage,
63 final CacheKeyGenerator cacheKeyGenerator,
64 final HttpCacheInvalidator cacheInvalidator) {
65 this.cacheUpdateHandler = new CacheUpdateHandler(resourceFactory);
66 this.cacheKeyGenerator = cacheKeyGenerator;
67 this.storage = storage;
68 this.cacheInvalidator = cacheInvalidator;
69 }
70
71 public BasicHttpCache(
72 final ResourceFactory resourceFactory,
73 final HttpCacheStorage storage,
74 final CacheKeyGenerator cacheKeyGenerator) {
75 this(resourceFactory, storage, cacheKeyGenerator, new DefaultCacheInvalidator());
76 }
77
78 public BasicHttpCache(final ResourceFactory resourceFactory, final HttpCacheStorage storage) {
79 this( resourceFactory, storage, new CacheKeyGenerator());
80 }
81
82 public BasicHttpCache(final CacheConfig config) {
83 this(new HeapResourceFactory(), new BasicHttpCacheStorage(config));
84 }
85
86 public BasicHttpCache() {
87 this(CacheConfig.DEFAULT);
88 }
89
90 @Override
91 public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) {
92 if (cacheEntry == null) {
93 return cacheKeyGenerator.generateKey(host, request);
94 } else {
95 return cacheKeyGenerator.generateKey(host, request, cacheEntry);
96 }
97 }
98
99 @Override
100 public void flushCacheEntriesFor(final HttpHost host, final HttpRequest request) {
101 if (LOG.isDebugEnabled()) {
102 LOG.debug("Flush cache entries: {}; {}", host, new RequestLine(request));
103 }
104 if (!Method.isSafe(request.getMethod())) {
105 final String cacheKey = cacheKeyGenerator.generateKey(host, request);
106 try {
107 storage.removeEntry(cacheKey);
108 } catch (final ResourceIOException ex) {
109 if (LOG.isWarnEnabled()) {
110 LOG.warn("I/O error removing cache entry with key {}", cacheKey);
111 }
112 }
113 }
114 }
115
116 @Override
117 public void flushCacheEntriesInvalidatedByRequest(final HttpHost host, final HttpRequest request) {
118 if (LOG.isDebugEnabled()) {
119 LOG.debug("Flush cache entries invalidated by request: {}; {}", host, new RequestLine(request));
120 }
121 cacheInvalidator.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyGenerator, storage);
122 }
123
124 @Override
125 public void flushCacheEntriesInvalidatedByExchange(final HttpHost host, final HttpRequest request, final HttpResponse response) {
126 if (LOG.isDebugEnabled()) {
127 LOG.debug("Flush cache entries invalidated by exchange: {}; {} -> {}", host, new RequestLine(request), new StatusLine(response));
128 }
129 if (!Method.isSafe(request.getMethod())) {
130 cacheInvalidator.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyGenerator, storage);
131 }
132 }
133
134 void storeInCache(
135 final String cacheKey,
136 final HttpHost host,
137 final HttpRequest request,
138 final HttpCacheEntry entry) {
139 if (entry.hasVariants()) {
140 storeVariantEntry(cacheKey, host, request, entry);
141 } else {
142 storeEntry(cacheKey, entry);
143 }
144 }
145
146 void storeEntry(final String cacheKey, final HttpCacheEntry entry) {
147 try {
148 storage.putEntry(cacheKey, entry);
149 } catch (final ResourceIOException ex) {
150 if (LOG.isWarnEnabled()) {
151 LOG.warn("I/O error storing cache entry with key {}", cacheKey);
152 }
153 }
154 }
155
156 void storeVariantEntry(
157 final String cacheKey,
158 final HttpHost host,
159 final HttpRequest req,
160 final HttpCacheEntry entry) {
161 final String variantKey = cacheKeyGenerator.generateVariantKey(req, entry);
162 final String variantCacheKey = cacheKeyGenerator.generateKey(host, req, entry);
163 storeEntry(variantCacheKey, entry);
164 try {
165 storage.updateEntry(cacheKey, existing -> cacheUpdateHandler.updateParentCacheEntry(req.getRequestUri(), existing, entry, variantKey, variantCacheKey));
166 } catch (final HttpCacheUpdateException ex) {
167 if (LOG.isWarnEnabled()) {
168 LOG.warn("Cannot update cache entry with key {}", cacheKey);
169 }
170 } catch (final ResourceIOException ex) {
171 if (LOG.isWarnEnabled()) {
172 LOG.warn("I/O error updating cache entry with key {}", cacheKey);
173 }
174 }
175 }
176
177 @Override
178 public void reuseVariantEntryFor(
179 final HttpHost host, final HttpRequest request, final Variant variant) {
180 if (LOG.isDebugEnabled()) {
181 LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), variant);
182 }
183 final String cacheKey = cacheKeyGenerator.generateKey(host, request);
184 final HttpCacheEntry entry = variant.getEntry();
185 final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry);
186 final String variantCacheKey = variant.getCacheKey();
187
188 try {
189 storage.updateEntry(cacheKey, existing -> cacheUpdateHandler.updateParentCacheEntry(request.getRequestUri(), existing, entry, variantKey, variantCacheKey));
190 } catch (final HttpCacheUpdateException ex) {
191 if (LOG.isWarnEnabled()) {
192 LOG.warn("Cannot update cache entry with key {}", cacheKey);
193 }
194 } catch (final ResourceIOException ex) {
195 if (LOG.isWarnEnabled()) {
196 LOG.warn("I/O error updating cache entry with key {}", cacheKey);
197 }
198 }
199 }
200
201 @Override
202 public HttpCacheEntry updateCacheEntry(
203 final HttpHost host,
204 final HttpRequest request,
205 final HttpCacheEntry stale,
206 final HttpResponse originResponse,
207 final Instant requestSent,
208 final Instant responseReceived) {
209 if (LOG.isDebugEnabled()) {
210 LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request));
211 }
212 final String cacheKey = cacheKeyGenerator.generateKey(host, request);
213 try {
214 final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
215 request.getRequestUri(),
216 stale,
217 requestSent,
218 responseReceived,
219 originResponse);
220 storeInCache(cacheKey, host, request, updatedEntry);
221 return updatedEntry;
222 } catch (final ResourceIOException ex) {
223 if (LOG.isWarnEnabled()) {
224 LOG.warn("I/O error updating cache entry with key {}", cacheKey);
225 }
226 return stale;
227 }
228 }
229
230 @Override
231 public HttpCacheEntry updateVariantCacheEntry(
232 final HttpHost host,
233 final HttpRequest request,
234 final HttpResponse originResponse,
235 final Variant variant,
236 final Instant requestSent,
237 final Instant responseReceived) {
238 if (LOG.isDebugEnabled()) {
239 LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), variant);
240 }
241 final HttpCacheEntry entry = variant.getEntry();
242 final String cacheKey = variant.getCacheKey();
243 try {
244 final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
245 request.getRequestUri(),
246 entry,
247 requestSent,
248 responseReceived,
249 originResponse);
250 storeEntry(cacheKey, updatedEntry);
251 return updatedEntry;
252 } catch (final ResourceIOException ex) {
253 if (LOG.isWarnEnabled()) {
254 LOG.warn("I/O error updating cache entry with key {}", cacheKey);
255 }
256 return entry;
257 }
258 }
259
260 @Override
261 public HttpCacheEntry createCacheEntry(
262 final HttpHost host,
263 final HttpRequest request,
264 final HttpResponse originResponse,
265 final ByteArrayBuffer content,
266 final Instant requestSent,
267 final Instant responseReceived) {
268 if (LOG.isDebugEnabled()) {
269 LOG.debug("Create cache entry: {}; {}", host, new RequestLine(request));
270 }
271 final String cacheKey = cacheKeyGenerator.generateKey(host, request);
272 try {
273 final HttpCacheEntry entry = cacheUpdateHandler.createCacheEntry(request, originResponse, content, requestSent, responseReceived);
274 storeInCache(cacheKey, host, request, entry);
275 return entry;
276 } catch (final ResourceIOException ex) {
277 if (LOG.isWarnEnabled()) {
278 LOG.warn("I/O error creating cache entry with key {}", cacheKey);
279 }
280 return new HttpCacheEntry(
281 requestSent,
282 responseReceived,
283 originResponse.getCode(),
284 originResponse.getHeaders(),
285 content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
286 }
287 }
288
289 @Override
290 public HttpCacheEntry getCacheEntry(final HttpHost host, final HttpRequest request) {
291 if (LOG.isDebugEnabled()) {
292 LOG.debug("Get cache entry: {}; {}", host, new RequestLine(request));
293 }
294 final String cacheKey = cacheKeyGenerator.generateKey(host, request);
295 final HttpCacheEntry root;
296 try {
297 root = storage.getEntry(cacheKey);
298 } catch (final ResourceIOException ex) {
299 if (LOG.isWarnEnabled()) {
300 LOG.warn("I/O error retrieving cache entry with key {}", cacheKey);
301 }
302 return null;
303 }
304 if (root == null) {
305 return null;
306 }
307 if (!root.hasVariants()) {
308 return root;
309 }
310 final String variantKey = cacheKeyGenerator.generateVariantKey(request, root);
311 final String variantCacheKey = root.getVariantMap().get(variantKey);
312 if (variantCacheKey == null) {
313 return null;
314 }
315 try {
316 return storage.getEntry(variantCacheKey);
317 } catch (final ResourceIOException ex) {
318 if (LOG.isWarnEnabled()) {
319 LOG.warn("I/O error retrieving cache entry with key {}", variantCacheKey);
320 }
321 return null;
322 }
323 }
324
325 @Override
326 public Map<String, Variant> getVariantCacheEntriesWithEtags(final HttpHost host, final HttpRequest request) {
327 if (LOG.isDebugEnabled()) {
328 LOG.debug("Get variant cache entries: {}; {}", host, new RequestLine(request));
329 }
330 final Map<String,Variant> variants = new HashMap<>();
331 final String cacheKey = cacheKeyGenerator.generateKey(host, request);
332 final HttpCacheEntry root;
333 try {
334 root = storage.getEntry(cacheKey);
335 } catch (final ResourceIOException ex) {
336 if (LOG.isWarnEnabled()) {
337 LOG.warn("I/O error retrieving cache entry with key {}", cacheKey);
338 }
339 return variants;
340 }
341 if (root != null && root.hasVariants()) {
342 for(final Map.Entry<String, String> variant : root.getVariantMap().entrySet()) {
343 final String variantCacheKey = variant.getValue();
344 try {
345 final HttpCacheEntry entry = storage.getEntry(variantCacheKey);
346 if (entry != null) {
347 final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG);
348 if (etagHeader != null) {
349 variants.put(etagHeader.getValue(), new Variant(variantCacheKey, entry));
350 }
351 }
352 } catch (final ResourceIOException ex) {
353 if (LOG.isWarnEnabled()) {
354 LOG.warn("I/O error retrieving cache entry with key {}", variantCacheKey);
355 }
356 return variants;
357 }
358 }
359 }
360 return variants;
361 }
362
363 }