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.http.impl.client.cache;
28  
29  import java.io.IOException;
30  import java.util.Date;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.concurrent.atomic.AtomicLong;
35  
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  import org.apache.http.Header;
39  import org.apache.http.HeaderElement;
40  import org.apache.http.HttpException;
41  import org.apache.http.HttpHost;
42  import org.apache.http.HttpMessage;
43  import org.apache.http.HttpRequest;
44  import org.apache.http.HttpResponse;
45  import org.apache.http.HttpStatus;
46  import org.apache.http.HttpVersion;
47  import org.apache.http.ProtocolVersion;
48  import org.apache.http.RequestLine;
49  import org.apache.http.annotation.ThreadSafe;
50  import org.apache.http.client.cache.CacheResponseStatus;
51  import org.apache.http.client.cache.HeaderConstants;
52  import org.apache.http.client.cache.HttpCacheContext;
53  import org.apache.http.client.cache.HttpCacheEntry;
54  import org.apache.http.client.cache.HttpCacheStorage;
55  import org.apache.http.client.cache.ResourceFactory;
56  import org.apache.http.client.methods.CloseableHttpResponse;
57  import org.apache.http.client.methods.HttpExecutionAware;
58  import org.apache.http.client.methods.HttpRequestWrapper;
59  import org.apache.http.client.protocol.HttpClientContext;
60  import org.apache.http.client.utils.DateUtils;
61  import org.apache.http.conn.routing.HttpRoute;
62  import org.apache.http.impl.execchain.ClientExecChain;
63  import org.apache.http.message.BasicHttpResponse;
64  import org.apache.http.protocol.HTTP;
65  import org.apache.http.protocol.HttpContext;
66  import org.apache.http.protocol.HttpCoreContext;
67  import org.apache.http.util.Args;
68  import org.apache.http.util.VersionInfo;
69  
70  /**
71   * Request executor in the request execution chain that is responsible for
72   * transparent client-side caching. The current implementation is conditionally
73   * compliant with HTTP/1.1 (meaning all the MUST and MUST NOTs are obeyed),
74   * although quite a lot, though not all, of the SHOULDs and SHOULD NOTs
75   * are obeyed too.
76   * <p/>
77   * Folks that would like to experiment with alternative storage backends
78   * should look at the {@link HttpCacheStorage} interface and the related
79   * package documentation there. You may also be interested in the provided
80   * {@link org.apache.http.impl.client.cache.ehcache.EhcacheHttpCacheStorage
81   * EhCache} and {@link
82   * org.apache.http.impl.client.cache.memcached.MemcachedHttpCacheStorage
83   * memcached} storage backends.
84   * <p/>
85   * Further responsibilities such as communication with the opposite
86   * endpoint is delegated to the next executor in the request execution
87   * chain.
88   *
89   * @since 4.3
90   */
91  @ThreadSafe // So long as the responseCache implementation is threadsafe
92  public class CachingExec implements ClientExecChain {
93  
94      private final static boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false;
95  
96      private final AtomicLong cacheHits = new AtomicLong();
97      private final AtomicLong cacheMisses = new AtomicLong();
98      private final AtomicLong cacheUpdates = new AtomicLong();
99  
100     private final Map<ProtocolVersion, String> viaHeaders = new HashMap<ProtocolVersion, String>(4);
101 
102     private final CacheConfig cacheConfig;
103     private final ClientExecChain backend;
104     private final HttpCache responseCache;
105     private final CacheValidityPolicy validityPolicy;
106     private final CachedHttpResponseGenerator responseGenerator;
107     private final CacheableRequestPolicy cacheableRequestPolicy;
108     private final CachedResponseSuitabilityChecker suitabilityChecker;
109     private final ConditionalRequestBuilder conditionalRequestBuilder;
110     private final ResponseProtocolCompliance responseCompliance;
111     private final RequestProtocolCompliance requestCompliance;
112     private final ResponseCachingPolicy responseCachingPolicy;
113 
114     private final AsynchronousValidator asynchRevalidator;
115 
116     private final Log log = LogFactory.getLog(getClass());
117 
118     public CachingExec(
119             final ClientExecChain backend,
120             final HttpCache cache,
121             final CacheConfig config) {
122         this(backend, cache, config, null);
123     }
124 
125     public CachingExec(
126             final ClientExecChain backend,
127             final HttpCache cache,
128             final CacheConfig config,
129             final AsynchronousValidator asynchRevalidator) {
130         super();
131         Args.notNull(backend, "HTTP backend");
132         Args.notNull(cache, "HttpCache");
133         this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
134         this.backend = backend;
135         this.responseCache = cache;
136         this.validityPolicy = new CacheValidityPolicy();
137         this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy);
138         this.cacheableRequestPolicy = new CacheableRequestPolicy();
139         this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy, this.cacheConfig);
140         this.conditionalRequestBuilder = new ConditionalRequestBuilder();
141         this.responseCompliance = new ResponseProtocolCompliance();
142         this.requestCompliance = new RequestProtocolCompliance(this.cacheConfig.isWeakETagOnPutDeleteAllowed());
143         this.responseCachingPolicy = new ResponseCachingPolicy(
144                 this.cacheConfig.getMaxObjectSize(), this.cacheConfig.isSharedCache(),
145                 this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(), this.cacheConfig.is303CachingEnabled());
146         this.asynchRevalidator = asynchRevalidator;
147     }
148 
149     public CachingExec(
150             final ClientExecChain backend,
151             final ResourceFactory resourceFactory,
152             final HttpCacheStorage storage,
153             final CacheConfig config) {
154         this(backend, new BasicHttpCache(resourceFactory, storage, config), config);
155     }
156 
157     public CachingExec(final ClientExecChain backend) {
158         this(backend, new BasicHttpCache(), CacheConfig.DEFAULT);
159     }
160 
161     CachingExec(
162             final ClientExecChain backend,
163             final HttpCache responseCache,
164             final CacheValidityPolicy validityPolicy,
165             final ResponseCachingPolicy responseCachingPolicy,
166             final CachedHttpResponseGenerator responseGenerator,
167             final CacheableRequestPolicy cacheableRequestPolicy,
168             final CachedResponseSuitabilityChecker suitabilityChecker,
169             final ConditionalRequestBuilder conditionalRequestBuilder,
170             final ResponseProtocolCompliance responseCompliance,
171             final RequestProtocolCompliance requestCompliance,
172             final CacheConfig config,
173             final AsynchronousValidator asynchRevalidator) {
174         this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
175         this.backend = backend;
176         this.responseCache = responseCache;
177         this.validityPolicy = validityPolicy;
178         this.responseCachingPolicy = responseCachingPolicy;
179         this.responseGenerator = responseGenerator;
180         this.cacheableRequestPolicy = cacheableRequestPolicy;
181         this.suitabilityChecker = suitabilityChecker;
182         this.conditionalRequestBuilder = conditionalRequestBuilder;
183         this.responseCompliance = responseCompliance;
184         this.requestCompliance = requestCompliance;
185         this.asynchRevalidator = asynchRevalidator;
186     }
187 
188     /**
189      * Reports the number of times that the cache successfully responded
190      * to an {@link HttpRequest} without contacting the origin server.
191      * @return the number of cache hits
192      */
193     public long getCacheHits() {
194         return cacheHits.get();
195     }
196 
197     /**
198      * Reports the number of times that the cache contacted the origin
199      * server because it had no appropriate response cached.
200      * @return the number of cache misses
201      */
202     public long getCacheMisses() {
203         return cacheMisses.get();
204     }
205 
206     /**
207      * Reports the number of times that the cache was able to satisfy
208      * a response by revalidating an existing but stale cache entry.
209      * @return the number of cache revalidations
210      */
211     public long getCacheUpdates() {
212         return cacheUpdates.get();
213     }
214 
215     public CloseableHttpResponse execute(
216             final HttpRoute route,
217             final HttpRequestWrapper request) throws IOException, HttpException {
218         return execute(route, request, HttpClientContext.create(), null);
219     }
220 
221     public CloseableHttpResponse execute(
222             final HttpRoute route,
223             final HttpRequestWrapper request,
224             final HttpClientContext context) throws IOException, HttpException {
225         return execute(route, request, context, null);
226     }
227 
228     @Override
229     public CloseableHttpResponse execute(
230             final HttpRoute route,
231             final HttpRequestWrapper request,
232             final HttpClientContext context,
233             final HttpExecutionAware execAware) throws IOException, HttpException {
234 
235         final HttpHost target = context.getTargetHost();
236         final String via = generateViaHeader(request.getOriginal());
237 
238         // default response context
239         setResponseStatus(context, CacheResponseStatus.CACHE_MISS);
240 
241         if (clientRequestsOurOptions(request)) {
242             setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
243             return Proxies.enhanceResponse(new OptionsHttp11Response());
244         }
245 
246         final HttpResponse fatalErrorResponse = getFatallyNoncompliantResponse(request, context);
247         if (fatalErrorResponse != null) {
248             return Proxies.enhanceResponse(fatalErrorResponse);
249         }
250 
251         requestCompliance.makeRequestCompliant(request);
252         request.addHeader("Via",via);
253 
254         flushEntriesInvalidatedByRequest(context.getTargetHost(), request);
255 
256         if (!cacheableRequestPolicy.isServableFromCache(request)) {
257             log.debug("Request is not servable from cache");
258             return callBackend(route, request, context, execAware);
259         }
260 
261         final HttpCacheEntry entry = satisfyFromCache(target, request);
262         if (entry == null) {
263             log.debug("Cache miss");
264             return handleCacheMiss(route, request, context, execAware);
265         } else {
266             return handleCacheHit(route, request, context, execAware, entry);
267         }
268     }
269 
270     private CloseableHttpResponse handleCacheHit(
271             final HttpRoute route,
272             final HttpRequestWrapper request,
273             final HttpClientContext context,
274             final HttpExecutionAware execAware,
275             final HttpCacheEntry entry) throws IOException, HttpException {
276         final HttpHost target = context.getTargetHost();
277         recordCacheHit(target, request);
278         CloseableHttpResponse out = null;
279         final Date now = getCurrentDate();
280         if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) {
281             log.debug("Cache hit");
282             out = generateCachedResponse(request, context, entry, now);
283         } else if (!mayCallBackend(request)) {
284             log.debug("Cache entry not suitable but only-if-cached requested");
285             out = generateGatewayTimeout(context);
286         } else if (!(entry.getStatusCode() == HttpStatus.SC_NOT_MODIFIED
287                 && !suitabilityChecker.isConditional(request))) {
288             log.debug("Revalidating cache entry");
289             return revalidateCacheEntry(route, request, context, execAware, entry, now);
290         } else {
291             log.debug("Cache entry not usable; calling backend");
292             return callBackend(route, request, context, execAware);
293         }
294         context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
295         context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target);
296         context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
297         context.setAttribute(HttpCoreContext.HTTP_RESPONSE, out);
298         context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.TRUE);
299         return out;
300     }
301 
302     private CloseableHttpResponse revalidateCacheEntry(
303             final HttpRoute route,
304             final HttpRequestWrapper request,
305             final HttpClientContext context,
306             final HttpExecutionAware execAware,
307             final HttpCacheEntry entry,
308             final Date now) throws HttpException {
309 
310         try {
311             if (asynchRevalidator != null
312                 && !staleResponseNotAllowed(request, entry, now)
313                 && validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) {
314                 log.trace("Serving stale with asynchronous revalidation");
315                 final CloseableHttpResponse resp = generateCachedResponse(request, context, entry, now);
316                 asynchRevalidator.revalidateCacheEntry(this, route, request, context, execAware, entry);
317                 return resp;
318             }
319             return revalidateCacheEntry(route, request, context, execAware, entry);
320         } catch (final IOException ioex) {
321             return handleRevalidationFailure(request, context, entry, now);
322         }
323     }
324 
325     private CloseableHttpResponse handleCacheMiss(
326             final HttpRoute route,
327             final HttpRequestWrapper request,
328             final HttpClientContext context,
329             final HttpExecutionAware execAware) throws IOException, HttpException {
330         final HttpHost target = context.getTargetHost();
331         recordCacheMiss(target, request);
332 
333         if (!mayCallBackend(request)) {
334             return Proxies.enhanceResponse(
335                     new BasicHttpResponse(
336                             HttpVersion.HTTP_1_1, HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout"));
337         }
338 
339         final Map<String, Variant> variants = getExistingCacheVariants(target, request);
340         if (variants != null && !variants.isEmpty()) {
341             return negotiateResponseFromVariants(route, request, context,
342                     execAware, variants);
343         }
344 
345         return callBackend(route, request, context, execAware);
346     }
347 
348     private HttpCacheEntry satisfyFromCache(
349             final HttpHost target, final HttpRequestWrapper request) {
350         HttpCacheEntry entry = null;
351         try {
352             entry = responseCache.getCacheEntry(target, request);
353         } catch (final IOException ioe) {
354             log.warn("Unable to retrieve entries from cache", ioe);
355         }
356         return entry;
357     }
358 
359     private HttpResponse getFatallyNoncompliantResponse(
360             final HttpRequestWrapper request,
361             final HttpContext context) {
362         HttpResponse fatalErrorResponse = null;
363         final List<RequestProtocolError> fatalError = requestCompliance.requestIsFatallyNonCompliant(request);
364 
365         for (final RequestProtocolError error : fatalError) {
366             setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
367             fatalErrorResponse = requestCompliance.getErrorForRequest(error);
368         }
369         return fatalErrorResponse;
370     }
371 
372     private Map<String, Variant> getExistingCacheVariants(
373             final HttpHost target,
374             final HttpRequestWrapper request) {
375         Map<String,Variant> variants = null;
376         try {
377             variants = responseCache.getVariantCacheEntriesWithEtags(target, request);
378         } catch (final IOException ioe) {
379             log.warn("Unable to retrieve variant entries from cache", ioe);
380         }
381         return variants;
382     }
383 
384     private void recordCacheMiss(final HttpHost target, final HttpRequestWrapper request) {
385         cacheMisses.getAndIncrement();
386         if (log.isTraceEnabled()) {
387             final RequestLine rl = request.getRequestLine();
388             log.trace("Cache miss [host: " + target + "; uri: " + rl.getUri() + "]");
389         }
390     }
391 
392     private void recordCacheHit(final HttpHost target, final HttpRequestWrapper request) {
393         cacheHits.getAndIncrement();
394         if (log.isTraceEnabled()) {
395             final RequestLine rl = request.getRequestLine();
396             log.trace("Cache hit [host: " + target + "; uri: " + rl.getUri() + "]");
397         }
398     }
399 
400     private void recordCacheUpdate(final HttpContext context) {
401         cacheUpdates.getAndIncrement();
402         setResponseStatus(context, CacheResponseStatus.VALIDATED);
403     }
404 
405     private void flushEntriesInvalidatedByRequest(
406             final HttpHost target,
407             final HttpRequestWrapper request) {
408         try {
409             responseCache.flushInvalidatedCacheEntriesFor(target, request);
410         } catch (final IOException ioe) {
411             log.warn("Unable to flush invalidated entries from cache", ioe);
412         }
413     }
414 
415     private CloseableHttpResponse generateCachedResponse(final HttpRequestWrapper request,
416             final HttpContext context, final HttpCacheEntry entry, final Date now) {
417         final CloseableHttpResponse cachedResponse;
418         if (request.containsHeader(HeaderConstants.IF_NONE_MATCH)
419                 || request.containsHeader(HeaderConstants.IF_MODIFIED_SINCE)) {
420             cachedResponse = responseGenerator.generateNotModifiedResponse(entry);
421         } else {
422             cachedResponse = responseGenerator.generateResponse(entry);
423         }
424         setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
425         if (validityPolicy.getStalenessSecs(entry, now) > 0L) {
426             cachedResponse.addHeader(HeaderConstants.WARNING,"110 localhost \"Response is stale\"");
427         }
428         return cachedResponse;
429     }
430 
431     private CloseableHttpResponse handleRevalidationFailure(
432             final HttpRequestWrapper request,
433             final HttpContext context,
434             final HttpCacheEntry entry,
435             final Date now) {
436         if (staleResponseNotAllowed(request, entry, now)) {
437             return generateGatewayTimeout(context);
438         } else {
439             return unvalidatedCacheHit(context, entry);
440         }
441     }
442 
443     private CloseableHttpResponse generateGatewayTimeout(
444             final HttpContext context) {
445         setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
446         return Proxies.enhanceResponse(new BasicHttpResponse(
447                 HttpVersion.HTTP_1_1, HttpStatus.SC_GATEWAY_TIMEOUT,
448                 "Gateway Timeout"));
449     }
450 
451     private CloseableHttpResponse unvalidatedCacheHit(
452             final HttpContext context, final HttpCacheEntry entry) {
453         final CloseableHttpResponse cachedResponse = responseGenerator.generateResponse(entry);
454         setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
455         cachedResponse.addHeader(HeaderConstants.WARNING, "111 localhost \"Revalidation failed\"");
456         return cachedResponse;
457     }
458 
459     private boolean staleResponseNotAllowed(
460             final HttpRequestWrapper request,
461             final HttpCacheEntry entry,
462             final Date now) {
463         return validityPolicy.mustRevalidate(entry)
464             || (cacheConfig.isSharedCache() && validityPolicy.proxyRevalidate(entry))
465             || explicitFreshnessRequest(request, entry, now);
466     }
467 
468     private boolean mayCallBackend(final HttpRequestWrapper request) {
469         for (final Header h: request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
470             for (final HeaderElement elt : h.getElements()) {
471                 if ("only-if-cached".equals(elt.getName())) {
472                     log.trace("Request marked only-if-cached");
473                     return false;
474                 }
475             }
476         }
477         return true;
478     }
479 
480     private boolean explicitFreshnessRequest(
481             final HttpRequestWrapper request,
482             final HttpCacheEntry entry,
483             final Date now) {
484         for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
485             for(final HeaderElement elt : h.getElements()) {
486                 if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) {
487                     try {
488                         final int maxstale = Integer.parseInt(elt.getValue());
489                         final long age = validityPolicy.getCurrentAgeSecs(entry, now);
490                         final long lifetime = validityPolicy.getFreshnessLifetimeSecs(entry);
491                         if (age - lifetime > maxstale) {
492                             return true;
493                         }
494                     } catch (final NumberFormatException nfe) {
495                         return true;
496                     }
497                 } else if (HeaderConstants.CACHE_CONTROL_MIN_FRESH.equals(elt.getName())
498                             || HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())) {
499                     return true;
500                 }
501             }
502         }
503         return false;
504     }
505 
506     private String generateViaHeader(final HttpMessage msg) {
507 
508         final ProtocolVersion pv = msg.getProtocolVersion();
509         final String existingEntry = viaHeaders.get(pv);
510         if (existingEntry != null) {
511             return existingEntry;
512         }
513 
514         final VersionInfo vi = VersionInfo.loadVersionInfo("org.apache.http.client", getClass().getClassLoader());
515         final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE;
516 
517         String value;
518         final Integer major = Integer.valueOf(pv.getMajor());
519         final Integer minor = Integer.valueOf(pv.getMinor());
520         if ("http".equalsIgnoreCase(pv.getProtocol())) {
521             value = String.format("%d.%d localhost (Apache-HttpClient/%s (cache))", major, minor,
522                     release);
523         } else {
524             value = String.format("%s/%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getProtocol(), major,
525                     minor, release);
526         }
527         viaHeaders.put(pv, value);
528 
529         return value;
530     }
531 
532     private void setResponseStatus(final HttpContext context, final CacheResponseStatus value) {
533         if (context != null) {
534             context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, value);
535         }
536     }
537 
538     /**
539      * Reports whether this {@code CachingHttpClient} implementation
540      * supports byte-range requests as specified by the {@code Range}
541      * and {@code Content-Range} headers.
542      * @return {@code true} if byte-range requests are supported
543      */
544     public boolean supportsRangeAndContentRangeHeaders() {
545         return SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS;
546     }
547 
548     Date getCurrentDate() {
549         return new Date();
550     }
551 
552     boolean clientRequestsOurOptions(final HttpRequest request) {
553         final RequestLine line = request.getRequestLine();
554 
555         if (!HeaderConstants.OPTIONS_METHOD.equals(line.getMethod())) {
556             return false;
557         }
558 
559         if (!"*".equals(line.getUri())) {
560             return false;
561         }
562 
563         if (!"0".equals(request.getFirstHeader(HeaderConstants.MAX_FORWARDS).getValue())) {
564             return false;
565         }
566 
567         return true;
568     }
569 
570     CloseableHttpResponse callBackend(
571             final HttpRoute route,
572             final HttpRequestWrapper request,
573             final HttpClientContext context,
574             final HttpExecutionAware execAware) throws IOException, HttpException  {
575 
576         final Date requestDate = getCurrentDate();
577 
578         log.trace("Calling the backend");
579         final CloseableHttpResponse backendResponse = backend.execute(route, request, context, execAware);
580         try {
581             backendResponse.addHeader("Via", generateViaHeader(backendResponse));
582             return handleBackendResponse(request, context, requestDate, getCurrentDate(),
583                     backendResponse);
584         } catch (final IOException ex) {
585             backendResponse.close();
586             throw ex;
587         } catch (final RuntimeException ex) {
588             backendResponse.close();
589             throw ex;
590         }
591     }
592 
593     private boolean revalidationResponseIsTooOld(final HttpResponse backendResponse,
594             final HttpCacheEntry cacheEntry) {
595         final Header entryDateHeader = cacheEntry.getFirstHeader(HTTP.DATE_HEADER);
596         final Header responseDateHeader = backendResponse.getFirstHeader(HTTP.DATE_HEADER);
597         if (entryDateHeader != null && responseDateHeader != null) {
598             final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
599             final Date respDate = DateUtils.parseDate(responseDateHeader.getValue());
600             if (entryDate == null || respDate == null) {
601                 // either backend response or cached entry did not have a valid
602                 // Date header, so we can't tell if they are out of order
603                 // according to the origin clock; thus we can skip the
604                 // unconditional retry recommended in 13.2.6 of RFC 2616.
605                 return false;
606             }
607             if (respDate.before(entryDate)) {
608                 return true;
609             }
610         }
611         return false;
612     }
613 
614     CloseableHttpResponse negotiateResponseFromVariants(
615             final HttpRoute route,
616             final HttpRequestWrapper request,
617             final HttpClientContext context,
618             final HttpExecutionAware execAware,
619             final Map<String, Variant> variants) throws IOException, HttpException {
620         final HttpRequestWrapper conditionalRequest = conditionalRequestBuilder
621             .buildConditionalRequestFromVariants(request, variants);
622 
623         final Date requestDate = getCurrentDate();
624         final CloseableHttpResponse backendResponse = backend.execute(
625                 route, conditionalRequest, context, execAware);
626         try {
627             final Date responseDate = getCurrentDate();
628 
629             backendResponse.addHeader("Via", generateViaHeader(backendResponse));
630 
631             if (backendResponse.getStatusLine().getStatusCode() != HttpStatus.SC_NOT_MODIFIED) {
632                 return handleBackendResponse(request, context, requestDate, responseDate,
633                         backendResponse);
634             }
635 
636             final Header resultEtagHeader = backendResponse.getFirstHeader(HeaderConstants.ETAG);
637             if (resultEtagHeader == null) {
638                 log.warn("304 response did not contain ETag");
639                 IOUtils.consume(backendResponse.getEntity());
640                 backendResponse.close();
641                 return callBackend(route, request, context, execAware);
642             }
643 
644             final String resultEtag = resultEtagHeader.getValue();
645             final Variant matchingVariant = variants.get(resultEtag);
646             if (matchingVariant == null) {
647                 log.debug("304 response did not contain ETag matching one sent in If-None-Match");
648                 IOUtils.consume(backendResponse.getEntity());
649                 backendResponse.close();
650                 return callBackend(route, request, context, execAware);
651             }
652 
653             final HttpCacheEntry matchedEntry = matchingVariant.getEntry();
654 
655             if (revalidationResponseIsTooOld(backendResponse, matchedEntry)) {
656                 IOUtils.consume(backendResponse.getEntity());
657                 backendResponse.close();
658                 return retryRequestUnconditionally(route, request, context, execAware, matchedEntry);
659             }
660 
661             recordCacheUpdate(context);
662 
663             final HttpCacheEntry responseEntry = getUpdatedVariantEntry(
664                 context.getTargetHost(), conditionalRequest, requestDate, responseDate,
665                     backendResponse, matchingVariant, matchedEntry);
666             backendResponse.close();
667 
668             final CloseableHttpResponse resp = responseGenerator.generateResponse(responseEntry);
669             tryToUpdateVariantMap(context.getTargetHost(), request, matchingVariant);
670 
671             if (shouldSendNotModifiedResponse(request, responseEntry)) {
672                 return responseGenerator.generateNotModifiedResponse(responseEntry);
673             }
674             return resp;
675         } catch (final IOException ex) {
676             backendResponse.close();
677             throw ex;
678         } catch (final RuntimeException ex) {
679             backendResponse.close();
680             throw ex;
681         }
682     }
683 
684     private CloseableHttpResponse retryRequestUnconditionally(
685             final HttpRoute route,
686             final HttpRequestWrapper request,
687             final HttpClientContext context,
688             final HttpExecutionAware execAware,
689             final HttpCacheEntry matchedEntry) throws IOException, HttpException {
690         final HttpRequestWrapper unconditional = conditionalRequestBuilder
691             .buildUnconditionalRequest(request, matchedEntry);
692         return callBackend(route, unconditional, context, execAware);
693     }
694 
695     private HttpCacheEntry getUpdatedVariantEntry(
696             final HttpHost target,
697             final HttpRequestWrapper conditionalRequest,
698             final Date requestDate,
699             final Date responseDate,
700             final CloseableHttpResponse backendResponse,
701             final Variant matchingVariant,
702             final HttpCacheEntry matchedEntry) throws IOException {
703         HttpCacheEntry responseEntry = matchedEntry;
704         try {
705             responseEntry = responseCache.updateVariantCacheEntry(target, conditionalRequest,
706                     matchedEntry, backendResponse, requestDate, responseDate, matchingVariant.getCacheKey());
707         } catch (final IOException ioe) {
708             log.warn("Could not update cache entry", ioe);
709         } finally {
710             backendResponse.close();
711         }
712         return responseEntry;
713     }
714 
715     private void tryToUpdateVariantMap(
716             final HttpHost target,
717             final HttpRequestWrapper request,
718             final Variant matchingVariant) {
719         try {
720             responseCache.reuseVariantEntryFor(target, request, matchingVariant);
721         } catch (final IOException ioe) {
722             log.warn("Could not update cache entry to reuse variant", ioe);
723         }
724     }
725 
726     private boolean shouldSendNotModifiedResponse(
727             final HttpRequestWrapper request,
728             final HttpCacheEntry responseEntry) {
729         return (suitabilityChecker.isConditional(request)
730                 && suitabilityChecker.allConditionalsMatch(request, responseEntry, new Date()));
731     }
732 
733     CloseableHttpResponse revalidateCacheEntry(
734             final HttpRoute route,
735             final HttpRequestWrapper request,
736             final HttpClientContext context,
737             final HttpExecutionAware execAware,
738             final HttpCacheEntry cacheEntry) throws IOException, HttpException {
739 
740         final HttpRequestWrapper conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(request, cacheEntry);
741 
742         Date requestDate = getCurrentDate();
743         CloseableHttpResponse backendResponse = backend.execute(
744                 route, conditionalRequest, context, execAware);
745         Date responseDate = getCurrentDate();
746 
747         if (revalidationResponseIsTooOld(backendResponse, cacheEntry)) {
748             backendResponse.close();
749             final HttpRequestWrapper unconditional = conditionalRequestBuilder
750                 .buildUnconditionalRequest(request, cacheEntry);
751             requestDate = getCurrentDate();
752             backendResponse = backend.execute(route, unconditional, context, execAware);
753             responseDate = getCurrentDate();
754         }
755 
756         backendResponse.addHeader(HeaderConstants.VIA, generateViaHeader(backendResponse));
757 
758         final int statusCode = backendResponse.getStatusLine().getStatusCode();
759         if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
760             recordCacheUpdate(context);
761         }
762 
763         if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
764             final HttpCacheEntry updatedEntry = responseCache.updateCacheEntry(
765                     context.getTargetHost(), request, cacheEntry,
766                     backendResponse, requestDate, responseDate);
767             if (suitabilityChecker.isConditional(request)
768                     && suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) {
769                 return responseGenerator
770                         .generateNotModifiedResponse(updatedEntry);
771             }
772             return responseGenerator.generateResponse(updatedEntry);
773         }
774 
775         if (staleIfErrorAppliesTo(statusCode)
776             && !staleResponseNotAllowed(request, cacheEntry, getCurrentDate())
777             && validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) {
778             try {
779                 final CloseableHttpResponse cachedResponse = responseGenerator.generateResponse(cacheEntry);
780                 cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
781                 return cachedResponse;
782             } finally {
783                 backendResponse.close();
784             }
785         }
786         return handleBackendResponse(conditionalRequest, context, requestDate, responseDate,
787                 backendResponse);
788     }
789 
790     private boolean staleIfErrorAppliesTo(final int statusCode) {
791         return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
792                 || statusCode == HttpStatus.SC_BAD_GATEWAY
793                 || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE
794                 || statusCode == HttpStatus.SC_GATEWAY_TIMEOUT;
795     }
796 
797     CloseableHttpResponse handleBackendResponse(
798             final HttpRequestWrapper request,
799             final HttpClientContext context,
800             final Date requestDate,
801             final Date responseDate,
802             final CloseableHttpResponse backendResponse) throws IOException {
803 
804         log.trace("Handling Backend response");
805         responseCompliance.ensureProtocolCompliance(request, backendResponse);
806 
807         final HttpHost target = context.getTargetHost();
808         final boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
809         responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse);
810         if (cacheable && !alreadyHaveNewerCacheEntry(target, request, backendResponse)) {
811             storeRequestIfModifiedSinceFor304Response(request, backendResponse);
812             return responseCache.cacheAndReturnResponse(target, request,
813                     backendResponse, requestDate, responseDate);
814         }
815         if (!cacheable) {
816             try {
817                 responseCache.flushCacheEntriesFor(target, request);
818             } catch (final IOException ioe) {
819                 log.warn("Unable to flush invalid cache entries", ioe);
820             }
821         }
822         return backendResponse;
823     }
824 
825     /**
826      * For 304 Not modified responses, adds a "Last-Modified" header with the
827      * value of the "If-Modified-Since" header passed in the request. This
828      * header is required to be able to reuse match the cache entry for
829      * subsequent requests but as defined in http specifications it is not
830      * included in 304 responses by backend servers. This header will not be
831      * included in the resulting response.
832      */
833     private void storeRequestIfModifiedSinceFor304Response(
834             final HttpRequest request, final HttpResponse backendResponse) {
835         if (backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED) {
836             final Header h = request.getFirstHeader("If-Modified-Since");
837             if (h != null) {
838                 backendResponse.addHeader("Last-Modified", h.getValue());
839             }
840         }
841     }
842 
843     private boolean alreadyHaveNewerCacheEntry(final HttpHost target, final HttpRequestWrapper request,
844             final HttpResponse backendResponse) {
845         HttpCacheEntry existing = null;
846         try {
847             existing = responseCache.getCacheEntry(target, request);
848         } catch (final IOException ioe) {
849             // nop
850         }
851         if (existing == null) {
852             return false;
853         }
854         final Header entryDateHeader = existing.getFirstHeader(HTTP.DATE_HEADER);
855         if (entryDateHeader == null) {
856             return false;
857         }
858         final Header responseDateHeader = backendResponse.getFirstHeader(HTTP.DATE_HEADER);
859         if (responseDateHeader == null) {
860             return false;
861         }
862         final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
863         final Date responseDate = DateUtils.parseDate(responseDateHeader.getValue());
864         if (entryDate == null || responseDate == null) {
865             return false;
866         }
867         return responseDate.before(entryDate);
868     }
869 
870 }