com.crashlytics.tools.android_2.1.0

ssingContentLengthHeader(response, entity);
      response.setEntity(entity);
    }
    long age = validityStrategy.getCurrentAgeSecs(entry, now);
    if (age > 0L) {
      if (age >= 2147483647L) {
        response.setHeader("Age", "2147483648");
      } else {
        response.setHeader("Age", "" + (int)age);
      }
    }
    return response;
  }
  
  HttpResponse generateNotModifiedResponse(HttpCacheEntry entry)
  {
    HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 304, "Not Modified");
    
    Header dateHeader = entry.getFirstHeader("Date");
    if (dateHeader == null) {
      dateHeader = new BasicHeader("Date", DateUtils.formatDate(new Date()));
    }
    response.addHeader(dateHeader);
    
    Header etagHeader = entry.getFirstHeader("ETag");
    if (etagHeader != null) {
      response.addHeader(etagHeader);
    }
    Header contentLocationHeader = entry.getFirstHeader("Content-Location");
    if (contentLocationHeader != null) {
      response.addHeader(contentLocationHeader);
    }
    Header expiresHeader = entry.getFirstHeader("Expires");
    if (expiresHeader != null) {
      response.addHeader(expiresHeader);
    }
    Header cacheControlHeader = entry.getFirstHeader("Cache-Control");
    if (cacheControlHeader != null) {
      response.addHeader(cacheControlHeader);
    }
    Header varyHeader = entry.getFirstHeader("Vary");
    if (varyHeader != null) {
      response.addHeader(varyHeader);
    }
    return response;
  }
  
  private void addMissingContentLengthHeader(HttpResponse response, HttpEntity entity)
  {
    if (transferEncodingIsPresent(response)) {
      return;
    }
    Header contentLength = response.getFirstHeader("Content-Length");
    if (contentLength == null)
    {
      contentLength = new BasicHeader("Content-Length", Long.toString(entity.getContentLength()));
      
      response.setHeader(contentLength);
    }
  }
  
  private boolean transferEncodingIsPresent(HttpResponse response)
  {
    Header hdr = response.getFirstHeader("Transfer-Encoding");
    return hdr != null;
  }
}

/* Location:
 * Qualified Name:     org.apache.http.impl.client.cache.CachedHttpResponseGenerator
 * Java Class Version: 5 (49.0)
 * JD-Core Version:    0.7.1
 */
package org.apache.http.impl.client.cache;

import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.annotation.Immutable;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.utils.DateUtils;

@Immutable
class CachedResponseSuitabilityChecker
{
  private final Log log = LogFactory.getLog(getClass());
  private final boolean sharedCache;
  private final boolean useHeuristicCaching;
  private final float heuristicCoefficient;
  private final long heuristicDefaultLifetime;
  private final CacheValidityPolicy validityStrategy;
  
  CachedResponseSuitabilityChecker(CacheValidityPolicy validityStrategy, CacheConfig config)
  {
    this.validityStrategy = validityStrategy;
    sharedCache = config.isSharedCache();
    useHeuristicCaching = config.isHeuristicCachingEnabled();
    heuristicCoefficient = config.getHeuristicCoefficient();
    heuristicDefaultLifetime = config.getHeuristicDefaultLifetime();
  }
  
  CachedResponseSuitabilityChecker(CacheConfig config)
  {
    this(new CacheValidityPolicy(), config);
  }
  
  private boolean isFreshEnough(HttpCacheEntry entry, HttpRequest request, Date now)
  {
    if (validityStrategy.isResponseFresh(entry, now)) {
      return true;
    }
    if ((useHeuristicCaching) && (validityStrategy.isResponseHeuristicallyFresh(entry, now, heuristicCoefficient, heuristicDefaultLifetime))) {
      return true;
    }
    if (originInsistsOnFreshness(entry)) {
      return false;
    }
    long maxstale = getMaxStale(request);
    if (maxstale == -1L) {
      return false;
    }
    return maxstale > validityStrategy.getStalenessSecs(entry, now);
  }
  
  private boolean originInsistsOnFreshness(HttpCacheEntry entry)
  {
    if (validityStrategy.mustRevalidate(entry)) {
      return true;
    }
    if (!sharedCache) {
      return false;
    }
    return (validityStrategy.proxyRevalidate(entry)) || (validityStrategy.hasCacheControlDirective(entry, "s-maxage"));
  }
  
  private long getMaxStale(HttpRequest request)
  {
    long maxstale = -1L;
    for (Header h : request.getHeaders("Cache-Control")) {
      for (HeaderElement elt : h.getElements()) {
        if ("max-stale".equals(elt.getName())) {
          if (((elt.getValue() == null) || ("".equals(elt.getValue().trim()))) && (maxstale == -1L)) {
            maxstale = Long.MAX_VALUE;
          } else {
            try
            {
              long val = Long.parseLong(elt.getValue());
              if (val < 0L) {
                val = 0L;
              }
              if ((maxstale == -1L) || (val < maxstale)) {
                maxstale = val;
              }
            }
            catch (NumberFormatException nfe)
            {
              maxstale = 0L;
            }
          }
        }
      }
    }
    return maxstale;
  }
  
  public boolean canCachedResponseBeUsed(HttpHost host, HttpRequest request, HttpCacheEntry entry, Date now)
  {
    if (!isFreshEnough(entry, request, now))
    {
      log.trace("Cache entry was not fresh enough");
      return false;
    }
    if (!validityStrategy.contentLengthHeaderMatchesActualLength(entry))
    {
      log.debug("Cache entry Content-Length and header information do not match");
      return false;
    }
    if (hasUnsupportedConditionalHeaders(request))
    {
      log.debug("Request contained conditional headers we don't handle");
      return false;
    }
    if ((!isConditional(request)) && (entry.getStatusCode() == 304)) {
      return false;
    }
    if ((isConditional(request)) && (!allConditionalsMatch(request, entry, now))) {
      return false;
    }
    for (Header ccHdr : request.getHeaders("Cache-Control")) {
      for (HeaderElement elt : ccHdr.getElements())
      {
        if ("no-cache".equals(elt.getName()))
        {
          log.trace("Response contained NO CACHE directive, cache was not suitable");
          return false;
        }
        if ("no-store".equals(elt.getName()))
        {
          log.trace("Response contained NO STORE directive, cache was not suitable");
          return false;
        }
        if ("max-age".equals(elt.getName())) {
          try
          {
            int maxage = Integer.parseInt(elt.getValue());
            if (validityStrategy.getCurrentAgeSecs(entry, now) > maxage)
            {
              log.trace("Response from cache was NOT suitable due to max age");
              return false;
            }
          }
          catch (NumberFormatException ex)
          {
            log.debug("Response from cache was malformed" + ex.getMessage());
            return false;
          }
        }
        if ("max-stale".equals(elt.getName())) {
          try
          {
            int maxstale = Integer.parseInt(elt.getValue());
            if (validityStrategy.getFreshnessLifetimeSecs(entry) > maxstale)
            {
              log.trace("Response from cache was not suitable due to Max stale freshness");
              return false;
            }
          }
          catch (NumberFormatException ex)
          {
            log.debug("Response from cache was malformed: " + ex.getMessage());
            return false;
          }
        }
        if ("min-fresh".equals(elt.getName())) {
          try
          {
            long minfresh = Long.parseLong(elt.getValue());
            if (minfresh < 0L) {
              return false;
            }
            long age = validityStrategy.getCurrentAgeSecs(entry, now);
            long freshness = validityStrategy.getFreshnessLifetimeSecs(entry);
            if (freshness - age < minfresh)
            {
              log.trace("Response from cache was not suitable due to min fresh freshness requirement");
              
              return false;
            }
          }
          catch (NumberFormatException ex)
          {
            log.debug("Response from cache was malformed: " + ex.getMessage());
            return false;
          }
        }
      }
    }
    log.trace("Response from cache was suitable");
    return true;
  }
  
  public boolean isConditional(HttpRequest request)
  {
    return (hasSupportedEtagValidator(request)) || (hasSupportedLastModifiedValidator(request));
  }
  
  public boolean allConditionalsMatch(HttpRequest request, HttpCacheEntry entry, Date now)
  {
    boolean hasEtagValidator = hasSupportedEtagValidator(request);
    boolean hasLastModifiedValidator = hasSupportedLastModifiedValidator(request);
    
    boolean etagValidatorMatches = (hasEtagValidator) && (etagValidatorMatches(request, entry));
    boolean lastModifiedValidatorMatches = (hasLastModifiedValidator) && (lastModifiedValidatorMatches(request, entry, now));
    if ((hasEtagValidator) && (hasLastModifiedValidator) && ((!etagValidatorMatches) || (!lastModifiedValidatorMatches))) {
      return false;
    }
    if ((hasEtagValidator) && (!etagValidatorMatches)) {
      return false;
    }
    if ((hasLastModifiedValidator) && (!lastModifiedValidatorMatches)) {
      return false;
    }
    return true;
  }
  
  private boolean hasUnsupportedConditionalHeaders(HttpRequest request)
  {
    return (request.getFirstHeader("If-Range") != null) || (request.getFirstHeader("If-Match") != null) || (hasValidDateField(request, "If-Unmodified-Since"));
  }
  
  private boolean hasSupportedEtagValidator(HttpRequest request)
  {
    return request.containsHeader("If-None-Match");
  }
  
  private boolean hasSupportedLastModifiedValidator(HttpRequest request)
  {
    return hasValidDateField(request, "If-Modified-Since");
  }
  
  private boolean etagValidatorMatches(HttpRequest request, HttpCacheEntry entry)
  {
    Header etagHeader = entry.getFirstHeader("ETag");
    String etag = etagHeader != null ? etagHeader.getValue() : null;
    Header[] ifNoneMatch = request.getHeaders("If-None-Match");
    if (ifNoneMatch != null) {
      for (Header h : ifNoneMatch) {
        for (HeaderElement elt : h.getElements())
        {
          String reqEtag = elt.toString();
          if ((("*".equals(reqEtag)) && (etag != null)) || (reqEtag.equals(etag))) {
            return true;
          }
        }
      }
    }
    return false;
  }
  
  private boolean lastModifiedValidatorMatches(HttpRequest request, HttpCacheEntry entry, Date now)
  {
    Header lastModifiedHeader = entry.getFirstHeader("Last-Modified");
    Date lastModified = null;
    if (lastModifiedHeader != null) {
      lastModified = DateUtils.parseDate(lastModifiedHeader.getValue());
    }
    if (lastModified == null) {
      return false;
    }
    for (Header h : request.getHeaders("If-Modified-Since"))
    {
      Date ifModifiedSince = DateUtils.parseDate(h.getValue());
      if ((ifModifiedSince != null) && (
        (ifModifiedSince.after(now)) || (lastModified.after(ifModifiedSince)))) {
        return false;
      }
    }
    return true;
  }
  
  private boolean hasValidDateField(HttpRequest request, String headerName)
  {
    Header[] arr$ = request.getHeaders(headerName);int len$ = arr$.length;int i$ = 0;
    if (i$ < len$)
    {
      Header h = arr$[i$];
      Date date = DateUtils.parseDate(h.getValue());
      return date != null;
    }
    return false;
  }
}

/* Location:
 * Qualified Name:     org.apache.http.impl.client.cache.CachedResponseSuitabilityChecker
 * Java Class Version: 5 (49.0)
 * JD-Core Version:    0.7.1
 */
package org.apache.http.impl.client.cache;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpMessage;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.ProtocolVersion;
import org.apache.http.RequestLine;
import org.apache.http.StatusLine;
import org.apache.http.annotation.ThreadSafe;
import org.apache.http.client.cache.CacheResponseStatus;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.ResourceFactory;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.DateUtils;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.execchain.ClientExecChain;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.Args;
import org.apache.http.util.EntityUtils;
import org.apache.http.util.VersionInfo;

@ThreadSafe
public class CachingExec
  implements ClientExecChain
{
  private static final boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false;
  private final AtomicLong cacheHits = new AtomicLong();
  private final AtomicLong cacheMisses = new AtomicLong();
  private final AtomicLong cacheUpdates = new AtomicLong();
  private final Map<ProtocolVersion, String> viaHeaders = new HashMap(4);
  private final CacheConfig cacheConfig;
  private final ClientExecChain backend;
  private final HttpCache responseCache;
  private final CacheValidityPolicy validityPolicy;
  private final CachedHttpResponseGenerator responseGenerator;
  private final CacheableRequestPolicy cacheableRequestPolicy;
  private final CachedResponseSuitabilityChecker suitabilityChecker;
  private final ConditionalRequestBuilder conditionalRequestBuilder;
  private final ResponseProtocolCompliance responseCompliance;
  private final RequestProtocolCompliance requestCompliance;
  private final ResponseCachingPolicy responseCachingPolicy;
  private final AsynchronousValidator asynchRevalidator;
  private final Log log = LogFactory.getLog(getClass());
  
  public CachingExec(ClientExecChain backend, HttpCache cache, CacheConfig config)
  {
    this(backend, cache, config, null);
  }
  
  public CachingExec(ClientExecChain backend, HttpCache cache, CacheConfig config, AsynchronousValidator asynchRevalidator)
  {
    Args.notNull(backend, "HTTP backend");
    Args.notNull(cache, "HttpCache");
    cacheConfig = (config != null ? config : CacheConfig.DEFAULT);
    this.backend = backend;
    responseCache = cache;
    validityPolicy = new CacheValidityPolicy();
    responseGenerator = new CachedHttpResponseGenerator(validityPolicy);
    cacheableRequestPolicy = new CacheableRequestPolicy();
    suitabilityChecker = new CachedResponseSuitabilityChecker(validityPolicy, config);
    conditionalRequestBuilder = new ConditionalRequestBuilder();
    responseCompliance = new ResponseProtocolCompliance();
    requestCompliance = new RequestProtocolCompliance(config.isWeakETagOnPutDeleteAllowed());
    responseCachingPolicy = new ResponseCachingPolicy(cacheConfig.getMaxObjectSize(), cacheConfig.isSharedCache(), cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(), cacheConfig.is303CachingEnabled());
    
    this.asynchRevalidator = asynchRevalidator;
  }
  
  public CachingExec(ClientExecChain backend, ResourceFactory resourceFactory, HttpCacheStorage storage, CacheConfig config)
  {
    this(backend, new BasicHttpCache(resourceFactory, storage, config), config);
  }
  
  public CachingExec(ClientExecChain backend)
  {
    this(backend, new BasicHttpCache(), CacheConfig.DEFAULT);
  }
  
  CachingExec(ClientExecChain backend, HttpCache responseCache, CacheValidityPolicy validityPolicy, ResponseCachingPolicy responseCachingPolicy, CachedHttpResponseGenerator responseGenerator, CacheableRequestPolicy cacheableRequestPolicy, CachedResponseSuitabilityChecker suitabilityChecker, ConditionalRequestBuilder conditionalRequestBuilder, ResponseProtocolCompliance responseCompliance, RequestProtocolCompliance requestCompliance, CacheConfig config, AsynchronousValidator asynchRevalidator)
  {
    cacheConfig = (config != null ? config : CacheConfig.DEFAULT);
    this.backend = backend;
    this.responseCache = responseCache;
    this.validityPolicy = validityPolicy;
    this.responseCachingPolicy = responseCachingPolicy;
    this.responseGenerator = responseGenerator;
    this.cacheableRequestPolicy = cacheableRequestPolicy;
    this.suitabilityChecker = suitabilityChecker;
    this.conditionalRequestBuilder = conditionalRequestBuilder;
    this.responseCompliance = responseCompliance;
    this.requestCompliance = requestCompliance;
    this.asynchRevalidator = asynchRevalidator;
  }
  
  public long getCacheHits()
  {
    return cacheHits.get();
  }
  
  public long getCacheMisses()
  {
    return cacheMisses.get();
  }
  
  public long getCacheUpdates()
  {
    return cacheUpdates.get();
  }
  
  public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request)
    throws IOException, HttpException
  {
    return execute(route, request, HttpClientContext.create(), null);
  }
  
  public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request, HttpClientContext context)
    throws IOException, HttpException
  {
    return execute(route, request, context, null);
  }
  
  public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request, HttpClientContext context, HttpExecutionAware execAware)
    throws IOException, HttpException
  {
    HttpHost target = route.getTargetHost();
    String via = generateViaHeader(request.getOriginal());
    
    setResponseStatus(context, CacheResponseStatus.CACHE_MISS);
    if (clientRequestsOurOptions(request))
    {
      setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
      return Proxies.enhanceResponse(new OptionsHttp11Response());
    }
    HttpResponse fatalErrorResponse = getFatallyNoncompliantResponse(request, context);
    if (fatalErrorResponse != null) {
      return Proxies.enhanceResponse(fatalErrorResponse);
    }
    requestCompliance.makeRequestCompliant(request);
    request.addHeader("Via", via);
    
    flushEntriesInvalidatedByRequest(route.getTargetHost(), request);
    if (!cacheableRequestPolicy.isServableFromCache(request))
    {
      log.debug("Request is not servable from cache");
      return callBackend(route, request, context, execAware);
    }
    HttpCacheEntry entry = satisfyFromCache(target, request);
    if (entry == null)
    {
      log.debug("Cache miss");
      return handleCacheMiss(route, request, context, execAware);
    }
    return handleCacheHit(route, request, context, execAware, entry);
  }
  
  private CloseableHttpResponse handleCacheHit(HttpRoute route, HttpRequestWrapper request, HttpClientContext context, HttpExecutionAware execAware, HttpCacheEntry entry)
    throws IOException, HttpException
  {
    HttpHost target = route.getTargetHost();
    recordCacheHit(target, request);
    CloseableHttpResponse out = null;
    Date now = getCurrentDate();
    if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now))
    {
      log.debug("Cache hit");
      out = Proxies.enhanceResponse(generateCachedResponse(request, context, entry, now));
    }
    else if (!mayCallBackend(request))
    {
      log.debug("Cache entry not suitable but only-if-cached requested");
      out = Proxies.enhanceResponse(generateGatewayTimeout(context));
    }
    else
    {
      if ((entry.getStatusCode() != 304) || (suitabilityChecker.isConditional(request)))
      {
        log.debug("Revalidating cache entry");
        return revalidateCacheEntry(route, request, context, execAware, entry, now);
      }
      log.debug("Cache entry not usable; calling backend");
      return callBackend(route, request, context, execAware);
    }
    context.setAttribute("http.route", route);
    context.setAttribute("http.target_host", target);
    context.setAttribute("http.request", request);
    context.setAttribute("http.response", out);
    context.setAttribute("http.request_sent", Boolean.TRUE);
    return out;
  }
  
  private CloseableHttpResponse revalidateCacheEntry(HttpRoute route, HttpRequestWrapper request, HttpClientContext context, HttpExecutionAware execAware, HttpCacheEntry entry, Date now)
    throws HttpException
  {
    try
    {
      if ((asynchRevalidator != null) && (!staleResponseNotAllowed(request, entry, now)) && (validityPolicy.mayReturnStaleWhileRevalidating(entry, now)))
      {
        log.trace("Serving stale with asynchronous revalidation");
        HttpResponse resp = generateCachedResponse(request, context, entry, now);
        asynchRevalidator.revalidateCacheEntry(this, route, request, context, execAware, entry);
        return Proxies.enhanceResponse(resp);
      }
      return revalidateCacheEntry(route, request, context, execAware, entry);
    }
    catch (IOException ioex) {}
    return Proxies.enhanceResponse(handleRevalidationFailure(request, context, entry, now));
  }
  
  private CloseableHttpResponse handleCacheMiss(HttpRoute route, HttpRequestWrapper request, HttpClientContext context, HttpExecutionAware execAware)
    throws IOException, HttpException
  {
    HttpHost target = route.getTargetHost();
    recordCacheMiss(target, request);
    if (!mayCallBackend(request)) {
      return Proxies.enhanceResponse(new BasicHttpResponse(HttpVersion.HTTP_1_1, 504, "Gateway Timeout"));
    }
    Map<String, Variant> variants = getExistingCacheVariants(target, request);
    if ((variants != null) && (variants.size() > 0)) {
      return Proxies.enhanceResponse(negotiateResponseFromVariants(route, request, context, execAware, variants));
    }
    return callBackend(route, request, context, execAware);
  }
  
  private HttpCacheEntry satisfyFromCache(HttpHost target, HttpRequestWrapper request)
  {
    HttpCacheEntry entry = null;
    try
    {
      entry = responseCache.getCacheEntry(target, request);
    }
    catch (IOException ioe)
    {
      log.warn("Unable to retrieve entries from cache", ioe);
    }
    return entry;
  }
  
  private HttpResponse getFatallyNoncompliantResponse(HttpRequestWrapper request, HttpContext context)
  {
    HttpResponse fatalErrorResponse = null;
    List<RequestProtocolError> fatalError = requestCompliance.requestIsFatallyNonCompliant(request);
    for (RequestProtocolError error : fatalError)
    {
      setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
      fatalErrorResponse = requestCompliance.getErrorForRequest(error);
    }
    return fatalErrorResponse;
  }
  
  private Map<String, Variant> getExistingCacheVariants(HttpHost target, HttpRequestWrapper request)
  {
    Map<String, Variant> variants = null;
    try
    {
      variants = responseCache.getVariantCacheEntriesWithEtags(target, request);
    }
    catch (IOException ioe)
    {
      log.warn("Unable to retrieve variant entries from cache", ioe);
    }
    return variants;
  }
  
  private void recordCacheMiss(HttpHost target, HttpRequestWrapper request)
  {
    cacheMisses.getAndIncrement();
    if (log.isTraceEnabled())
    {
      RequestLine rl = request.getRequestLine();
      log.trace("Cache miss [host: " + target + "; uri: " + rl.getUri() + "]");
    }
  }
  
  private void recordCacheHit(HttpHost target, HttpRequestWrapper request)
  {
    cacheHits.getAndIncrement();
    if (log.isTraceEnabled())
    {
      RequestLine rl = request.getRequestLine();
      log.trace("Cache hit [host: " + target + "; uri: " + rl.getUri() + "]");
    }
  }
  
  private void recordCacheUpdate(HttpContext context)
  {
    cacheUpdates.getAndIncrement();
    setResponseStatus(context, CacheResponseStatus.VALIDATED);
  }
  
  private void flushEntriesInvalidatedByRequest(HttpHost target, HttpRequestWrapper request)
  {
    try
    {
      responseCache.flushInvalidatedCacheEntriesFor(target, request);
    }
    catch (IOException ioe)
    {
      log.warn("Unable to flush invalidated entries from cache", ioe);
    }
  }
  
  private HttpResponse generateCachedResponse(HttpRequestWrapper request, HttpContext context, HttpCacheEntry entry, Date now)
  {
    HttpResponse cachedResponse;
    HttpResponse cachedResponse;
    if ((request.containsHeader("If-None-Match")) || (request.containsHeader("If-Modified-Since"))) {
      cachedResponse = responseGenerator.generateNotModifiedResponse(entry);
    } else {
      cachedResponse = responseGenerator.generateResponse(entry);
    }
    setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
    if (validityPolicy.getStalenessSecs(entry, now) > 0L) {
      cachedResponse.addHeader("Warning", "110 localhost \"Response is stale\"");
    }
    return cachedResponse;
  }
  
  private HttpResponse handleRevalidationFailure(HttpRequestWrapper request, HttpContext context, HttpCacheEntry entry, Date now)
  {
    if (staleResponseNotAllowed(request, entry, now)) {
      return generateGatewayTimeout(context);
    }
    return unvalidatedCacheHit(context, entry);
  }
  
  private HttpResponse generateGatewayTimeout(HttpContext context)
  {
    setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
    return new BasicHttpResponse(HttpVersion.HTTP_1_1, 504, "Gateway Timeout");
  }
  
  private HttpResponse unvalidatedCacheHit(HttpContext context, HttpCacheEntry entry)
  {
    HttpResponse cachedResponse = responseGenerator.generateResponse(entry);
    setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
    cachedResponse.addHeader("Warning", "111 localhost \"Revalidation failed\"");
    return cachedResponse;
  }
  
  private boolean staleResponseNotAllowed(HttpRequestWrapper request, HttpCacheEntry entry, Date now)
  {
    return (validityPolicy.mustRevalidate(entry)) || ((cacheConfig.isSharedCache()) && (validityPolicy.proxyRevalidate(entry))) || (explicitFreshnessRequest(request, entry, now));
  }
  
  private boolean mayCallBackend(HttpRequestWrapper request)
  {
    for (Header h : request.getHeaders("Cache-Control")) {
      for (HeaderElement elt : h.getElements()) {
        if ("only-if-cached".equals(elt.getName()))
        {
          log.trace("Request marked only-if-cached");
          return false;
        }
      }
    }
    return true;
  }
  
  private boolean explicitFreshnessRequest(HttpRequestWrapper request, HttpCacheEntry entry, Date now)
  {
    for (Header h : request.getHeaders("Cache-Control")) {
      for (HeaderElement elt : h.getElements())
      {
        if ("max-stale".equals(elt.getName())) {
          try
          {
            int maxstale = Integer.parseInt(elt.getValue());
            long age = validityPolicy.getCurrentAgeSecs(entry, now);
            long lifetime = validityPolicy.getFreshnessLifetimeSecs(entry);
            if (age - lifetime > maxstale) {
              return true;
            }
          }
          catch (NumberFormatException nfe)
          {
            return true;
          }
        }
        if (("min-fresh".equals(elt.getName())) || ("max-age".equals(elt.getName()))) {
          return true;
        }
      }
    }
    return false;
  }
  
  private String generateViaHeader(HttpMessage msg)
  {
    ProtocolVersion pv = msg.getProtocolVersion();
    String existingEntry = (String)viaHeaders.get(pv);
    if (existingEntry != null) {
      return existingEntry;
    }
    VersionInfo vi = VersionInfo.loadVersionInfo("org.apache.http.client", getClass().getClassLoader());
    String release = vi != null ? vi.getRelease() : "UNAVAILABLE";
    String value;
    String value;
    if ("http".equalsIgnoreCase(pv.getProtocol())) {
      value = String.format("%d.%d localhost (Apache-HttpClient/%s (cache))", new Object[] { Integer.valueOf(pv.getMajor()), Integer.valueOf(pv.getMinor()), release });
    } else {
      value = String.format("%s/%d.%d localhost (Apache-HttpClient/%s (cache))", new Object[] { pv.getProtocol(), Integer.valueOf(pv.getMajor()), Integer.valueOf(pv.getMinor()), release });
    }
    viaHeaders.put(pv, value);
    
    return value;
  }
  
  private void setResponseStatus(HttpContext context, CacheResponseStatus value)
  {
    if (context != null) {
      context.setAttribute("http.cache.response.status", value);
    }
  }
  
  public boolean supportsRangeAndContentRangeHeaders()
  {
    return false;
  }
  
  Date getCurrentDate()
  {
    return new Date();
  }
  
  boolean clientRequestsOurOptions(HttpRequest request)
  {
    RequestLine line = request.getRequestLine();
    if (!"OPTIONS".equals(line.getMethod())) {
      return false;
    }
    if (!"*".equals(line.getUri())) {
      return false;
    }
    if (!"0".equals(request.getFirstHeader("Max-Forwards").getValue())) {
      return false;
    }
    return true;
  }
  
  CloseableHttpResponse callBackend(HttpRoute route, HttpRequestWrapper request, HttpClientContext context, HttpExecutionAware execAware)
    throws IOException, HttpException
  {
    Date requestDate = getCurrentDate();
    
    log.trace("Calling the backend");
    CloseableHttpResponse backendResponse = backend.execute(route, request, context, execAware);
    try
    {
      backendResponse.addHeader("Via", generateViaHeader(backendResponse));
      return handleBackendResponse(route, request, context, execAware, requestDate, getCurrentDate(), backendResponse);
    }
    catch (IOException ex)
    {
      backendResponse.close();
      throw ex;
    }
    catch (RuntimeException ex)
    {
      backendResponse.close();
      throw ex;
    }
  }
  
  private boolean revalidationResponseIsTooOld(HttpResponse backendResponse, HttpCacheEntry cacheEntry)
  {
    Header entryDateHeader = cacheEntry.getFirstHeader("Date");
    Header responseDateHeader = backendResponse.getFirstHeader("Date");
    if ((entryDateHeader != null) && (responseDateHeader != null))
    {
      Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
      Date respDate = DateUtils.parseDate(responseDateHeader.getValue());
      if ((entryDate == null) || (respDate == null)) {
        return false;
      }
      if (respDate.before(entryDate)) {
        return true;
      }
    }
    return false;
  }
  
  HttpResponse negotiateResponseFromVariants(HttpRoute route, HttpRequestWrapper request, HttpClientContext context, HttpExecutionAware execAware, Map<String, Variant> variants)
    throws IOException, HttpException
  {
    HttpRequestWrapper conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(request, variants);
    
    Date requestDate = getCurrentDate();
    CloseableHttpResponse backendResponse = backend.execute(route, conditionalRequest, context, execAware);
    try
    {
      Date responseDate = getCurrentDate();
      
      backendResponse.addHeader("Via", generateViaHeader(backendResponse));
      if (backendResponse.getStatusLine().getStatusCode() != 304) {
        return handleBackendResponse(route, request, context, execAware, requestDate, responseDate, backendResponse);
      }
      Header resultEtagHeader = backendResponse.getFirstHeader("ETag");
      if (resultEtagHeader == null)
      {
        log.warn("304 response did not contain ETag");
        EntityUtils.consume(backendResponse.getEntity());
        backendResponse.close();
        return callBackend(route, request, context, execAware);
      }
      String resultEtag = resultEtagHeader.getValue();
      Variant matchingVariant = (Variant)variants.get(resultEtag);
      if (matchingVariant == null)
      {
        log.debug("304 response did not contain ETag matching one sent in If-None-Match");
        EntityUtils.consume(backendResponse.getEntity());
        backendResponse.close();
        return callBackend(route, request, context, execAware);
      }
      HttpCacheEntry matchedEntry = matchingVariant.getEntry();
      if (revalidationResponseIsTooOld(backendResponse, matchedEntry))
      {
        EntityUtils.consume(backendResponse.getEntity());
        backendResponse.close();
        return retryRequestUnconditionally(route, request, context, execAware, matchedEntry);
      }
      recordCacheUpdate(context);
      
      HttpCacheEntry responseEntry = getUpdatedVariantEntry(route.getTargetHost(), conditionalRequest, requestDate, responseDate, backendResponse, matchingVariant, matchedEntry);
      
      backendResponse.close();
      
      HttpResponse resp = responseGenerator.generateResponse(responseEntry);
      tryToUpdateVariantMap(route.getTargetHost(), request, matchingVariant);
      if (shouldSendNotModifiedResponse(request, responseEntry)) {
        return responseGenerator.generateNotModifiedResponse(responseEntry);
      }
      return resp;
    }
    catch (IOException ex)
    {
      backendResponse.close();
      throw ex;
    }
    catch (RuntimeException ex)
    {
      backendResponse.close();
      throw ex;
    }
  }
  
  private CloseableHttpResponse retryRequestUnconditionally(HttpRoute route, HttpRequestWrapper request, HttpClientContext context, HttpExecutionAware execAware, HttpCacheEntry matchedEntry)
    throws IOException, HttpException
  {
    HttpRequestWrapper unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request, matchedEntry);
    
    return callBackend(route, unconditional, context, execAware);
  }
  
  private HttpCacheEntry getUpdatedVariantEntry(HttpHost target, HttpRequestWrapper conditionalRequest, Date requestDate, Date responseDate, CloseableHttpResponse backendResponse, Variant matchingVariant, HttpCacheEntry matchedEntry)
    throws IOException
  {
    HttpCacheEntry responseEntry = matchedEntry;
    try
    {
      responseEntry = responseCache.updateVariantCacheEntry(target, conditionalRequest, matchedEntry, backendResponse, requestDate, responseDate, matchingVariant.getCacheKey());
    }
    catch (IOException ioe)
    {
      log.warn("Could not update cache entry", ioe);
    }
    finally
    {
      backendResponse.close();
    }
    return responseEntry;
  }
  
  private void tryToUpdateVariantMap(HttpHost target, HttpRequestWrapper request, Variant matchingVariant)
  {
    try
    {
      responseCache.reuseVariantEntryFor(target, request, matchingVariant);
    }
    catch (IOException ioe)
    {
      log.warn("Could not update cache entry to reuse variant", ioe);
    }
  }
  
  private boolean shouldSendNotModifiedResponse(HttpRequestWrapper request, HttpCacheEntry responseEntry)
  {
    return (suitabilityChecker.isConditional(request)) && (suitabilityChecker.allConditionalsMatch(request, responseEntry, new Date()));
  }
  
  CloseableHttpResponse revalidateCacheEntry(HttpRoute route, HttpRequestWrapper request, HttpClientContext context, HttpExecutionAware execAware, HttpCacheEntry cacheEntry)
    throws IOException, HttpException
  {
    HttpRequestWrapper conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(request, cacheEntry);
    
    Date requestDate = getCurrentDate();
    CloseableHttpResponse backendResponse = backend.execute(route, conditionalRequest, context, execAware);
    
    Date responseDate = getCurrentDate();
    if (revalidationResponseIsTooOld(backendResponse, cacheEntry))
    {
      backendResponse.close();
      HttpRequestWrapper unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request, cacheEntry);
      
      requestDate = getCurrentDate();
      backendResponse = backend.execute(route, unconditional, context, execAware);
      responseDate = getCurrentDate();
    }
    backendResponse.addHeader("Via", generateViaHeader(backendResponse));
    
    int statusCode = backendResponse.getStatusLine().getStatusCode();
    if ((statusCode == 304) || (statusCode == 200)) {
      recordCacheUpdate(context);
    }
    if (statusCode == 304)
    {
      HttpCacheEntry updatedEntry = responseCache.updateCacheEntry(route.getTargetHost(), request, cacheEntry, backendResponse, requestDate, responseDate);
      if ((suitabilityChecker.isConditional(request)) && (suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date()))) {
        return Proxies.enhanceResponse(responseGenerator.generateNotModifiedResponse(updatedEntry));
      }
      return Proxies.enhanceResponse(responseGenerator.generateResponse(updatedEntry));
    }
    if ((staleIfErrorAppliesTo(statusCode)) && (!staleResponseNotAllowed(request, cacheEntry, getCurrentDate())) && (validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate))) {
      try
      {
        HttpResponse cachedResponse = responseGenerator.generateResponse(cacheEntry);
        cachedResponse.addHeader("Warning", "110 localhost \"Response is stale\"");
        return Proxies.enhanceResponse(cachedResponse);
      }
      finally
      {
        backendResponse.close();
      }
    }
    return handleBackendResponse(route, conditionalRequest, context, execAware, requestDate, responseDate, backendResponse);
  }
  
  private boolean staleIfErrorAppliesTo(int statusCode)
  {
    return (statusCode == 500) || (statusCode == 502) || (statusCode == 503) || (statusCode == 504);
  }
  
  CloseableHttpResponse handleBackendResponse(HttpRoute route, HttpRequestWrapper request, HttpClientContext context, HttpExecutionAware execAware, Date requestDate, Date responseDate, CloseableHttpResponse backendResponse)
    throws IOException
  {
    log.trace("Handling Backend response");
    responseCompliance.ensureProtocolCompliance(request, backendResponse);
    
    HttpHost target = route.getTargetHost();
    boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
    responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse);
    if ((cacheable) && (!alreadyHaveNewerCacheEntry(target, request, backendResponse))) {
      try
      {
        storeRequestIfModifiedSinceFor304Response(request, backendResponse);
        return Proxies.enhanceResponse(responseCache.cacheAndReturnResponse(target, request, backendResponse, requestDate, responseDate));
      }
      finally
      {
        backendResponse.close();
      }
    }
    if (!cacheable) {
      try
      {
        responseCache.flushCacheEntriesFor(target, request);
      }
      catch (IOException ioe)
      {
        log.warn("Unable to flush invalid cache entries", ioe);
      }
    }
    return backendResponse;
  }
  
  private void storeRequestIfModifiedSinceFor304Response(HttpRequest request, HttpResponse backendResponse)
  {
    if (backendResponse.getStatusLine().getStatusCode() == 304)
    {
      Header h = request.getFirstHeader("If-Modified-Since");
      if (h != null) {
        backendResponse.addHeader("Last-Modified", h.getValue());
      }
    }
  }
  
  private boolean alreadyHaveNewerCacheEntry(HttpHost target, HttpRequestWrapper request, HttpResponse backendResponse)
  {
    HttpCacheEntry existing = null;
    try
    {
      existing = responseCache.getCacheEntry(target, request);
    }
    catch (IOException ioe) {}
    if (existing == null) {
      return false;
    }
    Header entryDateHeader = existing.getFirstHeader("Date");
    if (entryDateHeader == null) {
      return false;
    }
    Header responseDateHeader = backendResponse.getFirstHeader("Date");
    if (responseDateHeader == null) {
      return false;
    }
    Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
    Date responseDate = DateUtils.parseDate(responseDateHeader.getValue());
    if ((entryDate == null) || (responseDate == null)) {
      return false;
    }
    return responseDat
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109

Further reading...

For more information on Java 1.5 Tiger, you may find Java 1.5 Tiger, A developer's Notebook by D. Flanagan and B. McLaughlin from O'Reilly of interest.

New!JAR listings


Copyright 2006-2019. Infinite Loop Ltd