/** Returns the source to satisfy {@code request} given this cached response. */ public ResponseSource chooseResponseSource(long nowMillis, RequestHeaders request) { /* * If this response shouldn't have been stored, it should never be used * as a response source. This check should be redundant as long as the * persistence store is well-behaved and the rules are constant. */ if (!isCacheable(request)) { return ResponseSource.NETWORK; } if (request.isNoCache() || request.hasConditions()) { return ResponseSource.NETWORK; } long ageMillis = computeAge(nowMillis); long freshMillis = computeFreshnessLifetime(); if (request.getMaxAgeSeconds() != -1) { freshMillis = Math.min(freshMillis, TimeUnit.SECONDS.toMillis(request.getMaxAgeSeconds())); } long minFreshMillis = 0; if (request.getMinFreshSeconds() != -1) { minFreshMillis = TimeUnit.SECONDS.toMillis(request.getMinFreshSeconds()); } long maxStaleMillis = 0; if (!mustRevalidate && request.getMaxStaleSeconds() != -1) { maxStaleMillis = TimeUnit.SECONDS.toMillis(request.getMaxStaleSeconds()); } if (!noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { if (ageMillis + minFreshMillis >= freshMillis) { headers.add("Warning", "110 HttpURLConnection \"Response is stale\""); } /* * not available in API 8 if (ageMillis > TimeUnit.HOURS.toMillis(24) && isFreshnessLifetimeHeuristic()) { */ if (ageMillis > 24L * 60L * 60L * 1000L && isFreshnessLifetimeHeuristic()) { headers.add("Warning", "113 HttpURLConnection \"Heuristic expiration\""); } return ResponseSource.CACHE; } if (etag != null) { request.setIfNoneMatch(etag); } else if (lastModified != null) { request.setIfModifiedSince(lastModified); } else if (servedDate != null) { request.setIfModifiedSince(servedDate); } return request.hasConditions() ? ResponseSource.CONDITIONAL_CACHE : ResponseSource.NETWORK; }
/** Returns true if this response can be stored to later serve another request. */ public boolean isCacheable(RequestHeaders request) { /* * Always go to network for uncacheable response codes (RFC 2616, 13.4), * This implementation doesn't support caching partial content. */ int responseCode = headers.getResponseCode(); if (responseCode != HttpURLConnection.HTTP_OK && responseCode != HttpURLConnection.HTTP_NOT_AUTHORITATIVE && responseCode != HttpURLConnection.HTTP_MULT_CHOICE && responseCode != HttpURLConnection.HTTP_MOVED_PERM && responseCode != HttpURLConnection.HTTP_GONE) { return false; } /* * Responses to authorized requests aren't cacheable unless they include * a 'public', 'must-revalidate' or 's-maxage' directive. */ if (request.hasAuthorization() && !isPublic && !mustRevalidate && sMaxAgeSeconds == -1) { return false; } if (noStore) { return false; } return true; }
/** Combines this cached header with a network header as defined by RFC 2616, 13.5.3. */ public ResponseHeaders combine(ResponseHeaders network) { RawHeaders result = new RawHeaders(); for (int i = 0; i < headers.length(); i++) { String fieldName = headers.getFieldName(i); String value = headers.getValue(i); if (fieldName.equals("Warning") && value.startsWith("1")) { continue; // drop 100-level freshness warnings } if (!isEndToEnd(fieldName) || network.headers.get(fieldName) == null) { result.add(fieldName, value); } } for (int i = 0; i < network.headers.length(); i++) { String fieldName = network.headers.getFieldName(i); if (isEndToEnd(fieldName)) { result.add(fieldName, network.headers.getValue(i)); } } return new ResponseHeaders(uri, result); }
public void setLocalTimestamps(long sentRequestMillis, long receivedResponseMillis) { this.sentRequestMillis = sentRequestMillis; headers.add(SENT_MILLIS, Long.toString(sentRequestMillis)); this.receivedResponseMillis = receivedResponseMillis; headers.add(RECEIVED_MILLIS, Long.toString(receivedResponseMillis)); }
public void stripContentEncoding() { contentEncoding = null; headers.removeAll("Content-Encoding"); }
public ResponseHeaders(URI uri, RawHeaders headers) { this.uri = uri; this.headers = headers; HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() { @Override public void handle(String directive, String parameter) { if (directive.equalsIgnoreCase("no-cache")) { noCache = true; } else if (directive.equalsIgnoreCase("no-store")) { noStore = true; } else if (directive.equalsIgnoreCase("max-age")) { maxAgeSeconds = HeaderParser.parseSeconds(parameter); } else if (directive.equalsIgnoreCase("s-maxage")) { sMaxAgeSeconds = HeaderParser.parseSeconds(parameter); } else if (directive.equalsIgnoreCase("public")) { isPublic = true; } else if (directive.equalsIgnoreCase("must-revalidate")) { mustRevalidate = true; } } }; for (int i = 0; i < headers.length(); i++) { String fieldName = headers.getFieldName(i); String value = headers.getValue(i); if ("Cache-Control".equalsIgnoreCase(fieldName)) { HeaderParser.parseCacheControl(value, handler); } else if ("Date".equalsIgnoreCase(fieldName)) { servedDate = HttpDate.parse(value); } else if ("Expires".equalsIgnoreCase(fieldName)) { expires = HttpDate.parse(value); } else if ("Last-Modified".equalsIgnoreCase(fieldName)) { lastModified = HttpDate.parse(value); } else if ("ETag".equalsIgnoreCase(fieldName)) { etag = value; } else if ("Pragma".equalsIgnoreCase(fieldName)) { if (value.equalsIgnoreCase("no-cache")) { noCache = true; } } else if ("Age".equalsIgnoreCase(fieldName)) { ageSeconds = HeaderParser.parseSeconds(value); } else if ("Vary".equalsIgnoreCase(fieldName)) { // Replace the immutable empty set with something we can mutate. if (varyFields.isEmpty()) { varyFields = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); } for (String varyField : value.split(",")) { varyFields.add(varyField.trim()); } } else if ("Content-Encoding".equalsIgnoreCase(fieldName)) { contentEncoding = value; } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) { transferEncoding = value; } else if ("Content-Length".equalsIgnoreCase(fieldName)) { try { contentLength = Integer.parseInt(value); } catch (NumberFormatException ignored) { } } else if ("Connection".equalsIgnoreCase(fieldName)) { connection = value; } else if ("Proxy-Authenticate".equalsIgnoreCase(fieldName)) { proxyAuthenticate = value; } else if ("WWW-Authenticate".equalsIgnoreCase(fieldName)) { wwwAuthenticate = value; } else if (SENT_MILLIS.equalsIgnoreCase(fieldName)) { sentRequestMillis = Long.parseLong(value); } else if (RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) { receivedResponseMillis = Long.parseLong(value); } } }