Android Open Source - android Response Headers






From Project

Back to project page android.

License

The source code is released under:

GNU General Public License

If you think the Android project android listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2011 The Android Open Source Project
 *//from  w  w w . j  a  v  a  2s. com
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.squareup.okhttp.internal.http;

import com.squareup.okhttp.ResponseSource;
import com.squareup.okhttp.internal.Platform;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;

import static com.squareup.okhttp.internal.Util.equal;

/** Parsed HTTP response headers. */
public final class ResponseHeaders {

  /** HTTP header name for the local time when the request was sent. */
  private static final String SENT_MILLIS = Platform.get().getPrefix() + "-Sent-Millis";

  /** HTTP header name for the local time when the response was received. */
  private static final String RECEIVED_MILLIS = Platform.get().getPrefix() + "-Received-Millis";

  /** HTTP synthetic header with the response source. */
  static final String RESPONSE_SOURCE = Platform.get().getPrefix() + "-Response-Source";

  /** HTTP synthetic header with the selected transport (spdy/3, http/1.1, etc). */
  static final String SELECTED_TRANSPORT = Platform.get().getPrefix() + "-Selected-Transport";

  private final URI uri;
  private final RawHeaders headers;

  /** The server's time when this response was served, if known. */
  private Date servedDate;

  /** The last modified date of the response, if known. */
  private Date lastModified;

  /**
   * The expiration date of the response, if known. If both this field and the
   * max age are set, the max age is preferred.
   */
  private Date expires;

  /**
   * Extension header set by HttpURLConnectionImpl specifying the timestamp
   * when the HTTP request was first initiated.
   */
  private long sentRequestMillis;

  /**
   * Extension header set by HttpURLConnectionImpl specifying the timestamp
   * when the HTTP response was first received.
   */
  private long receivedResponseMillis;

  /**
   * In the response, this field's name "no-cache" is misleading. It doesn't
   * prevent us from caching the response; it only means we have to validate
   * the response with the origin server before returning it. We can do this
   * with a conditional get.
   */
  private boolean noCache;

  /** If true, this response should not be cached. */
  private boolean noStore;

  /**
   * The duration past the response's served date that it can be served
   * without validation.
   */
  private int maxAgeSeconds = -1;

  /**
   * The "s-maxage" directive is the max age for shared caches. Not to be
   * confused with "max-age" for non-shared caches, As in Firefox and Chrome,
   * this directive is not honored by this cache.
   */
  private int sMaxAgeSeconds = -1;

  /**
   * This request header field's name "only-if-cached" is misleading. It
   * actually means "do not use the network". It is set by a client who only
   * wants to make a request if it can be fully satisfied by the cache.
   * Cached responses that would require validation (ie. conditional gets) are
   * not permitted if this header is set.
   */
  private boolean isPublic;
  private boolean mustRevalidate;
  private String etag;
  private int ageSeconds = -1;

  /** Case-insensitive set of field names. */
  private Set<String> varyFields = Collections.emptySet();

  private String contentEncoding;
  private String transferEncoding;
  private long contentLength = -1;
  private String connection;
  private String contentType;

  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 ("no-cache".equalsIgnoreCase(directive)) {
          noCache = true;
        } else if ("no-store".equalsIgnoreCase(directive)) {
          noStore = true;
        } else if ("max-age".equalsIgnoreCase(directive)) {
          maxAgeSeconds = HeaderParser.parseSeconds(parameter);
        } else if ("s-maxage".equalsIgnoreCase(directive)) {
          sMaxAgeSeconds = HeaderParser.parseSeconds(parameter);
        } else if ("public".equalsIgnoreCase(directive)) {
          isPublic = true;
        } else if ("must-revalidate".equalsIgnoreCase(directive)) {
          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 ("no-cache".equalsIgnoreCase(value)) {
          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 = Long.parseLong(value);
        } catch (NumberFormatException ignored) {
        }
      } else if ("Content-Type".equalsIgnoreCase(fieldName)) {
        contentType = value;
      } else if ("Connection".equalsIgnoreCase(fieldName)) {
        connection = value;
      } else if (SENT_MILLIS.equalsIgnoreCase(fieldName)) {
        sentRequestMillis = Long.parseLong(value);
      } else if (RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
        receivedResponseMillis = Long.parseLong(value);
      }
    }
  }

  public boolean isContentEncodingGzip() {
    return "gzip".equalsIgnoreCase(contentEncoding);
  }

  public void stripContentEncoding() {
    contentEncoding = null;
    headers.removeAll("Content-Encoding");
  }

  public void stripContentLength() {
    contentLength = -1;
    headers.removeAll("Content-Length");
  }

  public boolean isChunked() {
    return "chunked".equalsIgnoreCase(transferEncoding);
  }

  public boolean hasConnectionClose() {
    return "close".equalsIgnoreCase(connection);
  }

  public URI getUri() {
    return uri;
  }

  public RawHeaders getHeaders() {
    return headers;
  }

  public Date getServedDate() {
    return servedDate;
  }

  public Date getLastModified() {
    return lastModified;
  }

  public Date getExpires() {
    return expires;
  }

  public boolean isNoCache() {
    return noCache;
  }

  public boolean isNoStore() {
    return noStore;
  }

  public int getMaxAgeSeconds() {
    return maxAgeSeconds;
  }

  public int getSMaxAgeSeconds() {
    return sMaxAgeSeconds;
  }

  public boolean isPublic() {
    return isPublic;
  }

  public boolean isMustRevalidate() {
    return mustRevalidate;
  }

  public String getEtag() {
    return etag;
  }

  public Set<String> getVaryFields() {
    return varyFields;
  }

  public String getContentEncoding() {
    return contentEncoding;
  }

  public long getContentLength() {
    return contentLength;
  }

  public String getContentType() {
    return contentType;
  }

  public String getConnection() {
    return connection;
  }

  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 setResponseSource(ResponseSource responseSource) {
    headers.set(RESPONSE_SOURCE, responseSource.toString() + " " + headers.getResponseCode());
  }

  public void setTransport(String transport) {
    headers.set(SELECTED_TRANSPORT, transport);
  }

  /**
   * Returns the current age of the response, in milliseconds. The calculation
   * is specified by RFC 2616, 13.2.3 Age Calculations.
   */
  private long computeAge(long nowMillis) {
    long apparentReceivedAge =
        servedDate != null ? Math.max(0, receivedResponseMillis - servedDate.getTime()) : 0;
    long receivedAge =
        ageSeconds != -1 ? Math.max(apparentReceivedAge, TimeUnit.SECONDS.toMillis(ageSeconds))
            : apparentReceivedAge;
    long responseDuration = receivedResponseMillis - sentRequestMillis;
    long residentDuration = nowMillis - receivedResponseMillis;
    return receivedAge + responseDuration + residentDuration;
  }

  /**
   * Returns the number of milliseconds that the response was fresh for,
   * starting from the served date.
   */
  private long computeFreshnessLifetime() {
    if (maxAgeSeconds != -1) {
      return TimeUnit.SECONDS.toMillis(maxAgeSeconds);
    } else if (expires != null) {
      long servedMillis = servedDate != null ? servedDate.getTime() : receivedResponseMillis;
      long delta = expires.getTime() - servedMillis;
      return delta > 0 ? delta : 0;
    } else if (lastModified != null && uri.getRawQuery() == null) {
      // As recommended by the HTTP RFC and implemented in Firefox, the
      // max age of a document should be defaulted to 10% of the
      // document's age at the time it was served. Default expiration
      // dates aren't used for URIs containing a query.
      long servedMillis = servedDate != null ? servedDate.getTime() : sentRequestMillis;
      long delta = servedMillis - lastModified.getTime();
      return delta > 0 ? (delta / 10) : 0;
    }
    return 0;
  }

  /**
   * Returns true if computeFreshnessLifetime used a heuristic. If we used a
   * heuristic to serve a cached response older than 24 hours, we are required
   * to attach a warning.
   */
  private boolean isFreshnessLifetimeHeuristic() {
    return maxAgeSeconds == -1 && expires == null;
  }

  /**
   * 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;
  }

  /**
   * Returns true if a Vary header contains an asterisk. Such responses cannot
   * be cached.
   */
  public boolean hasVaryAll() {
    return varyFields.contains("*");
  }

  /**
   * Returns true if none of the Vary headers on this response have changed
   * between {@code cachedRequest} and {@code newRequest}.
   */
  public boolean varyMatches(Map<String, List<String>> cachedRequest,
      Map<String, List<String>> newRequest) {
    for (String field : varyFields) {
      if (!equal(cachedRequest.get(field), newRequest.get(field))) {
        return false;
      }
    }
    return true;
  }

  /** 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\"");
      }
      long oneDayMillis = 24 * 60 * 60 * 1000L;
      if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
        headers.add("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
      }
      return ResponseSource.CACHE;
    }

    if (lastModified != null) {
      request.setIfModifiedSince(lastModified);
    } else if (servedDate != null) {
      request.setIfModifiedSince(servedDate);
    }

    if (etag != null) {
      request.setIfNoneMatch(etag);
    }

    return request.hasConditions() ? ResponseSource.CONDITIONAL_CACHE : ResponseSource.NETWORK;
  }

  /**
   * Returns true if this cached response should be used; false if the
   * network response should be used.
   */
  public boolean validate(ResponseHeaders networkResponse) {
    if (networkResponse.headers.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
      return true;
    }

    // The HTTP spec says that if the network's response is older than our
    // cached response, we may return the cache's response. Like Chrome (but
    // unlike Firefox), this client prefers to return the newer response.
    if (lastModified != null
        && networkResponse.lastModified != null
        && networkResponse.lastModified.getTime() < lastModified.getTime()) {
      return true;
    }

    return false;
  }

  /**
   * Combines this cached header with a network header as defined by RFC 2616,
   * 13.5.3.
   */
  public ResponseHeaders combine(ResponseHeaders network) throws IOException {
    RawHeaders result = new RawHeaders();
    result.setStatusLine(headers.getStatusLine());

    for (int i = 0; i < headers.length(); i++) {
      String fieldName = headers.getFieldName(i);
      String value = headers.getValue(i);
      if ("Warning".equals(fieldName) && 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);
  }

  /**
   * Returns true if {@code fieldName} is an end-to-end HTTP header, as
   * defined by RFC 2616, 13.5.1.
   */
  private static boolean isEndToEnd(String fieldName) {
    return !"Connection".equalsIgnoreCase(fieldName)
        && !"Keep-Alive".equalsIgnoreCase(fieldName)
        && !"Proxy-Authenticate".equalsIgnoreCase(fieldName)
        && !"Proxy-Authorization".equalsIgnoreCase(fieldName)
        && !"TE".equalsIgnoreCase(fieldName)
        && !"Trailers".equalsIgnoreCase(fieldName)
        && !"Transfer-Encoding".equalsIgnoreCase(fieldName)
        && !"Upgrade".equalsIgnoreCase(fieldName);
  }
}




Java Source Code List

com.phonegap.helloworld.BuildConfig.java
com.phonegap.helloworld.HelloWorld.java
com.squareup.okhttp.Address.java
com.squareup.okhttp.ConnectionPool.java
com.squareup.okhttp.Connection.java
com.squareup.okhttp.Dispatcher.java
com.squareup.okhttp.Failure.java
com.squareup.okhttp.HttpResponseCache.java
com.squareup.okhttp.Job.java
com.squareup.okhttp.MediaType.java
com.squareup.okhttp.OkAuthenticator.java
com.squareup.okhttp.OkHttpClient.java
com.squareup.okhttp.OkResponseCache.java
com.squareup.okhttp.Request.java
com.squareup.okhttp.ResponseSource.java
com.squareup.okhttp.Response.java
com.squareup.okhttp.RouteDatabase.java
com.squareup.okhttp.Route.java
com.squareup.okhttp.TunnelRequest.java
com.squareup.okhttp.internal.AbstractOutputStream.java
com.squareup.okhttp.internal.Base64.java
com.squareup.okhttp.internal.DiskLruCache.java
com.squareup.okhttp.internal.Dns.java
com.squareup.okhttp.internal.FaultRecoveringOutputStream.java
com.squareup.okhttp.internal.NamedRunnable.java
com.squareup.okhttp.internal.Platform.java
com.squareup.okhttp.internal.StrictLineReader.java
com.squareup.okhttp.internal.Util.java
com.squareup.okhttp.internal.http.AbstractHttpInputStream.java
com.squareup.okhttp.internal.http.HeaderParser.java
com.squareup.okhttp.internal.http.HttpAuthenticator.java
com.squareup.okhttp.internal.http.HttpDate.java
com.squareup.okhttp.internal.http.HttpEngine.java
com.squareup.okhttp.internal.http.HttpTransport.java
com.squareup.okhttp.internal.http.HttpURLConnectionImpl.java
com.squareup.okhttp.internal.http.HttpsEngine.java
com.squareup.okhttp.internal.http.HttpsURLConnectionImpl.java
com.squareup.okhttp.internal.http.OkResponseCacheAdapter.java
com.squareup.okhttp.internal.http.Policy.java
com.squareup.okhttp.internal.http.RawHeaders.java
com.squareup.okhttp.internal.http.RequestHeaders.java
com.squareup.okhttp.internal.http.ResponseHeaders.java
com.squareup.okhttp.internal.http.RetryableOutputStream.java
com.squareup.okhttp.internal.http.RouteSelector.java
com.squareup.okhttp.internal.http.SpdyTransport.java
com.squareup.okhttp.internal.http.Transport.java
com.squareup.okhttp.internal.http.UnknownLengthHttpInputStream.java
com.squareup.okhttp.internal.spdy.ErrorCode.java
com.squareup.okhttp.internal.spdy.FrameReader.java
com.squareup.okhttp.internal.spdy.FrameWriter.java
com.squareup.okhttp.internal.spdy.HeadersMode.java
com.squareup.okhttp.internal.spdy.Hpack.java
com.squareup.okhttp.internal.spdy.Http20Draft06.java
com.squareup.okhttp.internal.spdy.IncomingStreamHandler.java
com.squareup.okhttp.internal.spdy.NameValueBlockReader.java
com.squareup.okhttp.internal.spdy.Ping.java
com.squareup.okhttp.internal.spdy.Settings.java
com.squareup.okhttp.internal.spdy.Spdy3.java
com.squareup.okhttp.internal.spdy.SpdyConnection.java
com.squareup.okhttp.internal.spdy.SpdyStream.java
com.squareup.okhttp.internal.spdy.Variant.java
com.squareup.okhttp.internal.tls.DistinguishedNameParser.java
com.squareup.okhttp.internal.tls.OkHostnameVerifier.java
org.apache.cordova.App.java
org.apache.cordova.AuthenticationToken.java
org.apache.cordova.BuildConfig.java
org.apache.cordova.CallbackContext.java
org.apache.cordova.Config.java
org.apache.cordova.CordovaActivity.java
org.apache.cordova.CordovaArgs.java
org.apache.cordova.CordovaChromeClient.java
org.apache.cordova.CordovaInterface.java
org.apache.cordova.CordovaPlugin.java
org.apache.cordova.CordovaResourceApi.java
org.apache.cordova.CordovaUriHelper.java
org.apache.cordova.CordovaWebViewClient.java
org.apache.cordova.CordovaWebView.java
org.apache.cordova.DirectoryManager.java
org.apache.cordova.DroidGap.java
org.apache.cordova.ExifHelper.java
org.apache.cordova.ExposedJsApi.java
org.apache.cordova.FileHelper.java
org.apache.cordova.IceCreamCordovaWebViewClient.java
org.apache.cordova.JSONUtils.java
org.apache.cordova.LOG.java
org.apache.cordova.LinearLayoutSoftKeyboardDetect.java
org.apache.cordova.NativeToJsMessageQueue.java
org.apache.cordova.PluginEntry.java
org.apache.cordova.PluginManager.java
org.apache.cordova.PluginResult.java
org.apache.cordova.ScrollEvent.java
org.apache.cordova.Whitelist.java