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