mirror of
https://github.com/square/okhttp.git
synced 2026-01-25 16:01:38 +03:00
Improve cache strategy.
This renames ResponseStrategy to CacheStrategy and cleans up the code that calls into it. This fixes a bug where we were incorrectly reporting stats when the caller was requesting only-if-cached. In those cases we were tracking stats before applying that constraint. Tests for these cases have been added
This commit is contained in:
@@ -118,10 +118,7 @@ public final class Request {
|
||||
}
|
||||
|
||||
public Builder newBuilder() {
|
||||
return new Builder(url)
|
||||
.method(method, body)
|
||||
.headers(headers)
|
||||
.tag(tag);
|
||||
return new Builder(this);
|
||||
}
|
||||
|
||||
public boolean isChunked() {
|
||||
@@ -397,23 +394,27 @@ public final class Request {
|
||||
|
||||
public static class Builder {
|
||||
private URL url;
|
||||
private String method = "GET";
|
||||
private Headers.Builder headers = new Headers.Builder();
|
||||
private String method;
|
||||
private final Headers.Builder headers;
|
||||
private Body body;
|
||||
private Object tag;
|
||||
|
||||
public Builder(String url) {
|
||||
url(url);
|
||||
public Builder() {
|
||||
this.method = "GET";
|
||||
this.headers = new Headers.Builder();
|
||||
}
|
||||
|
||||
public Builder(URL url) {
|
||||
url(url);
|
||||
private Builder(Request request) {
|
||||
this.url = request.url;
|
||||
this.method = request.method;
|
||||
this.body = request.body;
|
||||
this.tag = request.tag;
|
||||
this.headers = request.headers.newBuilder();
|
||||
}
|
||||
|
||||
public Builder url(String url) {
|
||||
try {
|
||||
this.url = new URL(url);
|
||||
return this;
|
||||
return url(new URL(url));
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException("Malformed URL: " + url);
|
||||
}
|
||||
@@ -443,12 +444,6 @@ public final class Request {
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO: this shouldn't be public.
|
||||
public Builder headers(Headers headers) {
|
||||
this.headers = headers.newBuilder();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setChunked() {
|
||||
headers.set("Transfer-Encoding", "chunked");
|
||||
return this;
|
||||
@@ -514,25 +509,6 @@ public final class Request {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// TODO: this shouldn't be public.
|
||||
/**
|
||||
* @param method like "GET", "POST", "HEAD", etc.
|
||||
* @param path like "/foo/bar.html"
|
||||
* @param version like "HTTP/1.1"
|
||||
* @param host like "www.android.com:1234"
|
||||
* @param scheme like "https"
|
||||
*/
|
||||
public Builder addSpdyRequestHeaders(
|
||||
String method, String path, String version, String host, String scheme) {
|
||||
// TODO: populate the statusLine for the client's benefit?
|
||||
headers.set(":method", method);
|
||||
headers.set(":scheme", scheme);
|
||||
headers.set(":path", path);
|
||||
headers.set(":version", version);
|
||||
headers.set(":host", host);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder get() {
|
||||
return method("GET", null);
|
||||
}
|
||||
@@ -569,6 +545,7 @@ public final class Request {
|
||||
}
|
||||
|
||||
public Request build() {
|
||||
if (url == null) throw new IllegalStateException("url == null");
|
||||
return new Request(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
package com.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.Platform;
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.http.HeaderParser;
|
||||
import com.squareup.okhttp.internal.http.Headers;
|
||||
import com.squareup.okhttp.internal.http.HttpDate;
|
||||
import com.squareup.okhttp.internal.http.SyntheticHeaders;
|
||||
import com.squareup.okhttp.internal.http.StatusLine;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
@@ -47,21 +47,6 @@ import static com.squareup.okhttp.internal.Util.equal;
|
||||
* This class is in beta. APIs are subject to change!
|
||||
*/
|
||||
public final class Response {
|
||||
/** 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. */
|
||||
// TODO: this shouldn't be public.
|
||||
public static final String RESPONSE_SOURCE = Platform.get().getPrefix() + "-Response-Source";
|
||||
|
||||
/** HTTP synthetic header with the selected transport (spdy/3, http/1.1, etc). */
|
||||
// TODO: this shouldn't be public.
|
||||
public static final String SELECTED_TRANSPORT
|
||||
= Platform.get().getPrefix() + "-Selected-Transport";
|
||||
|
||||
private final Request request;
|
||||
private final StatusLine statusLine;
|
||||
private final Handshake handshake;
|
||||
@@ -561,9 +546,9 @@ public final class Response {
|
||||
contentType = value;
|
||||
} else if ("Connection".equalsIgnoreCase(fieldName)) {
|
||||
connection = value;
|
||||
} else if (SENT_MILLIS.equalsIgnoreCase(fieldName)) {
|
||||
} else if (SyntheticHeaders.SENT_MILLIS.equalsIgnoreCase(fieldName)) {
|
||||
sentRequestMillis = Long.parseLong(value);
|
||||
} else if (RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
|
||||
} else if (SyntheticHeaders.RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
|
||||
receivedResponseMillis = Long.parseLong(value);
|
||||
}
|
||||
}
|
||||
@@ -691,14 +676,14 @@ public final class Response {
|
||||
|
||||
// TODO: this shouldn't be public.
|
||||
public Builder setLocalTimestamps(long sentRequestMillis, long receivedResponseMillis) {
|
||||
headers.set(SENT_MILLIS, Long.toString(sentRequestMillis));
|
||||
headers.set(RECEIVED_MILLIS, Long.toString(receivedResponseMillis));
|
||||
headers.set(SyntheticHeaders.SENT_MILLIS, Long.toString(sentRequestMillis));
|
||||
headers.set(SyntheticHeaders.RECEIVED_MILLIS, Long.toString(receivedResponseMillis));
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO: this shouldn't be public.
|
||||
public Builder setResponseSource(ResponseSource responseSource) {
|
||||
headers.set(RESPONSE_SOURCE, responseSource.toString() + " " + statusLine.code());
|
||||
headers.set(SyntheticHeaders.RESPONSE_SOURCE, responseSource + " " + statusLine.code());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,14 @@ public enum ResponseSource {
|
||||
CONDITIONAL_CACHE,
|
||||
|
||||
/** The response was returned from the network. */
|
||||
NETWORK;
|
||||
NETWORK,
|
||||
|
||||
/**
|
||||
* The request demanded a cached response that the cache couldn't satisfy.
|
||||
* This yields a 504 (Gateway Timeout) response as specified by
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4.
|
||||
*/
|
||||
NONE;
|
||||
|
||||
public boolean requiresConnection() {
|
||||
return this == CONDITIONAL_CACHE || this == NETWORK;
|
||||
|
||||
@@ -60,7 +60,8 @@ public final class TunnelRequest {
|
||||
* the proxy unencrypted.
|
||||
*/
|
||||
Request getRequest() throws IOException {
|
||||
Request.Builder result = new Request.Builder(new URL("https", host, port, "/"));
|
||||
Request.Builder result = new Request.Builder()
|
||||
.url(new URL("https", host, port, "/"));
|
||||
|
||||
// Always set Host and User-Agent.
|
||||
result.header("Host", port == getDefaultPort("https") ? host : (host + ":" + port));
|
||||
|
||||
@@ -7,16 +7,19 @@ import java.net.HttpURLConnection;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Given a request and cached response, this figures out the next action. It
|
||||
* may also update the request to add conditions, or the response to add
|
||||
* warnings.
|
||||
* Given a request and cached response, this figures out whether to use the
|
||||
* network, the cache, or both.
|
||||
*
|
||||
* <p>Selecting the next action may have side effects. The request may gain
|
||||
* conditions such as an "If-None-Match" or "If-Modified-Since" header. The
|
||||
* response may gain a warning if it is potentially stale.
|
||||
*/
|
||||
public final class ResponseStrategy {
|
||||
public final class CacheStrategy {
|
||||
public final Request request;
|
||||
public final Response response;
|
||||
public final ResponseSource source;
|
||||
|
||||
private ResponseStrategy(
|
||||
private CacheStrategy(
|
||||
Request request, Response response, ResponseSource source) {
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
@@ -111,17 +114,17 @@ public final class ResponseStrategy {
|
||||
* Returns a strategy to satisfy {@code request} using the a cached response
|
||||
* {@code response}.
|
||||
*/
|
||||
public static ResponseStrategy get(
|
||||
public static CacheStrategy get(
|
||||
long nowMillis, Response response, Request 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(response, request)) {
|
||||
return new ResponseStrategy(request, response, ResponseSource.NETWORK);
|
||||
return new CacheStrategy(request, response, ResponseSource.NETWORK);
|
||||
}
|
||||
|
||||
if (request.isNoCache() || request.hasConditions()) {
|
||||
return new ResponseStrategy(request, response, ResponseSource.NETWORK);
|
||||
return new CacheStrategy(request, response, ResponseSource.NETWORK);
|
||||
}
|
||||
|
||||
long ageMillis = computeAge(response, nowMillis);
|
||||
@@ -142,7 +145,8 @@ public final class ResponseStrategy {
|
||||
}
|
||||
|
||||
if (!response.isNoCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
|
||||
Response.Builder builder = response.newBuilder();
|
||||
Response.Builder builder = response.newBuilder()
|
||||
.setResponseSource(ResponseSource.CACHE); // Overwrite any stored response source.
|
||||
if (ageMillis + minFreshMillis >= freshMillis) {
|
||||
builder.addWarning("110 HttpURLConnection \"Response is stale\"");
|
||||
}
|
||||
@@ -150,7 +154,7 @@ public final class ResponseStrategy {
|
||||
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic(response)) {
|
||||
builder.addWarning("113 HttpURLConnection \"Heuristic expiration\"");
|
||||
}
|
||||
return new ResponseStrategy(request, builder.build(), ResponseSource.CACHE);
|
||||
return new CacheStrategy(request, builder.build(), ResponseSource.CACHE);
|
||||
}
|
||||
|
||||
Request.Builder conditionalRequestBuilder = request.newBuilder();
|
||||
@@ -169,6 +173,6 @@ public final class ResponseStrategy {
|
||||
ResponseSource responseSource = conditionalRequest.hasConditions()
|
||||
? ResponseSource.CONDITIONAL_CACHE
|
||||
: ResponseSource.NETWORK;
|
||||
return new ResponseStrategy(conditionalRequest, response, responseSource);
|
||||
return new CacheStrategy(conditionalRequest, response, responseSource);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ import com.squareup.okhttp.Response;
|
||||
import com.squareup.okhttp.ResponseSource;
|
||||
import com.squareup.okhttp.TunnelRequest;
|
||||
import com.squareup.okhttp.internal.Dns;
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -40,6 +39,7 @@ import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import static com.squareup.okhttp.internal.Util.EMPTY_INPUT_STREAM;
|
||||
import static com.squareup.okhttp.internal.Util.closeQuietly;
|
||||
import static com.squareup.okhttp.internal.Util.getDefaultPort;
|
||||
import static com.squareup.okhttp.internal.Util.getEffectivePort;
|
||||
import static com.squareup.okhttp.internal.http.StatusLine.HTTP_CONTINUE;
|
||||
@@ -155,33 +155,10 @@ public class HttpEngine {
|
||||
* writing the request body if it exists.
|
||||
*/
|
||||
public final void sendRequest() throws IOException {
|
||||
if (responseSource != null) {
|
||||
return;
|
||||
}
|
||||
if (responseSource != null) return;
|
||||
|
||||
prepareRawRequestHeaders();
|
||||
initResponseSource();
|
||||
OkResponseCache responseCache = client.getOkResponseCache();
|
||||
if (responseCache != null) {
|
||||
responseCache.trackResponse(responseSource);
|
||||
}
|
||||
|
||||
// The raw response source may require the network, but the request
|
||||
// headers may forbid network use. In that case, dispose of the network
|
||||
// response and use a gateway timeout response instead, as specified
|
||||
// by http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4.
|
||||
if (request.isOnlyIfCached() && responseSource.requiresConnection()) {
|
||||
if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
|
||||
Util.closeQuietly(validatingResponse.body());
|
||||
}
|
||||
this.responseSource = ResponseSource.CACHE;
|
||||
|
||||
this.validatingResponse = new Response.Builder(request)
|
||||
.statusLine(new StatusLine("HTTP/1.1 504 Gateway Timeout"))
|
||||
.body(EMPTY_BODY)
|
||||
.build();
|
||||
promoteValidatingResponse();
|
||||
}
|
||||
responseSource = chooseResponseSource();
|
||||
|
||||
if (responseSource.requiresConnection()) {
|
||||
sendSocketRequest();
|
||||
@@ -191,38 +168,56 @@ public class HttpEngine {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the source for this response. It may be corrected later if the
|
||||
* request headers forbids network use.
|
||||
*/
|
||||
private void initResponseSource() throws IOException {
|
||||
responseSource = ResponseSource.NETWORK;
|
||||
|
||||
/** Returns a source that can satisfy the request. */
|
||||
private ResponseSource chooseResponseSource() throws IOException {
|
||||
OkResponseCache responseCache = client.getOkResponseCache();
|
||||
if (responseCache == null) return;
|
||||
if (responseCache == null) return ResponseSource.NETWORK; // No cache? Easy decision.
|
||||
|
||||
Response candidate = responseCache.get(request);
|
||||
if (candidate == null) return;
|
||||
|
||||
// Drop the cached response if it's missing a required handshake.
|
||||
if (request.isHttps() && candidate.handshake() == null) {
|
||||
Util.closeQuietly(candidate.body());
|
||||
return;
|
||||
ResponseSource result;
|
||||
if (candidate == null) {
|
||||
result = ResponseSource.NETWORK;
|
||||
|
||||
} else if (request.isHttps() && candidate.handshake() == null) {
|
||||
// Drop the cached response if it's missing a required handshake.
|
||||
result = ResponseSource.NETWORK;
|
||||
|
||||
} else {
|
||||
// We've got a lead on a cached response. Ask response strategy to analyze it.
|
||||
long now = System.currentTimeMillis();
|
||||
CacheStrategy cacheStrategy = CacheStrategy.get(now, candidate, request);
|
||||
result = cacheStrategy.source;
|
||||
this.request = cacheStrategy.request;
|
||||
|
||||
if (result == ResponseSource.CACHE || result == ResponseSource.CONDITIONAL_CACHE) {
|
||||
this.validatingResponse = cacheStrategy.response;
|
||||
}
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
ResponseStrategy responseStrategy = ResponseStrategy.get(now, candidate, request);
|
||||
this.responseSource = responseStrategy.source;
|
||||
this.request = responseStrategy.request;
|
||||
if (candidate != null && result == ResponseSource.NETWORK) {
|
||||
closeQuietly(candidate.body()); // We aren't using the cached response. Close it.
|
||||
}
|
||||
|
||||
if (responseSource == ResponseSource.CACHE) {
|
||||
this.validatingResponse = responseStrategy.response;
|
||||
if (result == ResponseSource.CACHE) {
|
||||
promoteValidatingResponse();
|
||||
} else if (request.isOnlyIfCached()) {
|
||||
// We're forbidden from using the network, but the cache is insufficient.
|
||||
if (result == ResponseSource.CONDITIONAL_CACHE) {
|
||||
closeQuietly(validatingResponse.body());
|
||||
}
|
||||
|
||||
result = ResponseSource.NONE;
|
||||
this.validatingResponse = new Response.Builder(request)
|
||||
.statusLine(new StatusLine("HTTP/1.1 504 Gateway Timeout"))
|
||||
.setResponseSource(result)
|
||||
.body(EMPTY_BODY)
|
||||
.build();
|
||||
promoteValidatingResponse();
|
||||
} else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
|
||||
this.validatingResponse = responseStrategy.response;
|
||||
} else if (responseSource == ResponseSource.NETWORK) {
|
||||
Util.closeQuietly(candidate.body());
|
||||
}
|
||||
|
||||
responseCache.trackResponse(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Response cacheableResponse() {
|
||||
@@ -314,9 +309,7 @@ public class HttpEngine {
|
||||
|
||||
/** Returns the request body or null if this request doesn't have a body. */
|
||||
public final OutputStream getRequestBody() {
|
||||
if (responseSource == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (responseSource == null) throw new IllegalStateException();
|
||||
return requestBodyOut;
|
||||
}
|
||||
|
||||
@@ -349,7 +342,7 @@ public class HttpEngine {
|
||||
if (responseCache == null) return;
|
||||
|
||||
// Should we cache this response for this request?
|
||||
if (!ResponseStrategy.isCacheable(response, request)) {
|
||||
if (!CacheStrategy.isCacheable(response, request)) {
|
||||
responseCache.maybeRemove(request);
|
||||
return;
|
||||
}
|
||||
@@ -382,7 +375,7 @@ public class HttpEngine {
|
||||
if (validatingResponse != null
|
||||
&& validatingResponse.body() != null
|
||||
&& responseBodyIn == validatingResponse.body().byteStream()) {
|
||||
Util.closeQuietly(responseBodyIn);
|
||||
closeQuietly(responseBodyIn);
|
||||
}
|
||||
|
||||
if (!connectionReleased && connection != null) {
|
||||
@@ -390,7 +383,7 @@ public class HttpEngine {
|
||||
|
||||
if (transport == null
|
||||
|| !transport.makeReusable(streamCanceled, requestBodyOut, responseTransferIn)) {
|
||||
Util.closeQuietly(connection);
|
||||
closeQuietly(connection);
|
||||
connection = null;
|
||||
} else if (automaticallyReleaseConnectionToPool) {
|
||||
client.getConnectionPool().recycle(connection);
|
||||
@@ -460,7 +453,7 @@ public class HttpEngine {
|
||||
}
|
||||
|
||||
if (request.getHost() == null) {
|
||||
result.setHost(getHostHeader(request.url()));
|
||||
result.setHost(hostHeader(request.url()));
|
||||
}
|
||||
|
||||
if ((connection == null || connection.getHttpMinorVersion() != 0)
|
||||
@@ -490,7 +483,7 @@ public class HttpEngine {
|
||||
return agent != null ? agent : ("Java" + System.getProperty("java.version"));
|
||||
}
|
||||
|
||||
public static String getHostHeader(URL url) {
|
||||
public static String hostHeader(URL url) {
|
||||
return getEffectivePort(url) != getDefaultPort(url.getProtocol())
|
||||
? url.getHost() + ":" + url.getPort()
|
||||
: url.getHost();
|
||||
@@ -501,19 +494,9 @@ public class HttpEngine {
|
||||
* headers and starts reading the HTTP response body if it exists.
|
||||
*/
|
||||
public final void readResponse() throws IOException {
|
||||
if (hasResponse()) {
|
||||
// TODO: this doesn't make much sense.
|
||||
response = response.newBuilder().setResponseSource(responseSource).build();
|
||||
return;
|
||||
}
|
||||
|
||||
if (responseSource == null) {
|
||||
throw new IllegalStateException("readResponse() without sendRequest()");
|
||||
}
|
||||
|
||||
if (!responseSource.requiresConnection()) {
|
||||
return;
|
||||
}
|
||||
if (response != null) return;
|
||||
if (responseSource == null) throw new IllegalStateException("call sendRequest() first!");
|
||||
if (!responseSource.requiresConnection()) return;
|
||||
|
||||
if (sentRequestMillis == -1) {
|
||||
if (request.getContentLength() == -1
|
||||
@@ -556,7 +539,7 @@ public class HttpEngine {
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
Util.closeQuietly(validatingResponse.body());
|
||||
closeQuietly(validatingResponse.body());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ public final class HttpTransport implements Transport {
|
||||
|
||||
Response.Builder responseBuilder = new Response.Builder(request);
|
||||
responseBuilder.statusLine(statusLine);
|
||||
responseBuilder.header(Response.SELECTED_TRANSPORT, "http/1.1");
|
||||
responseBuilder.header(SyntheticHeaders.SELECTED_TRANSPORT, "http/1.1");
|
||||
|
||||
Headers.Builder headersBuilder = new Headers.Builder();
|
||||
headersBuilder.readHeaders(in);
|
||||
|
||||
@@ -172,7 +172,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection implements Policy {
|
||||
}
|
||||
|
||||
// For the request line property assigned to the null key, just use no proxy and HTTP 1.1.
|
||||
Request request = new Request.Builder(getURL()).method(method, null).build();
|
||||
Request request = new Request.Builder().url(getURL()).method(method, null).build();
|
||||
String requestLine = RequestLine.get(request, null, 1);
|
||||
return requestHeaders.build().toMultimap(requestLine);
|
||||
}
|
||||
@@ -271,10 +271,14 @@ public class HttpURLConnectionImpl extends HttpURLConnection implements Policy {
|
||||
|
||||
private HttpEngine newHttpEngine(String method, Connection connection,
|
||||
RetryableOutputStream requestBody) throws IOException {
|
||||
Request request = new Request.Builder(getURL())
|
||||
.method(method, null) // No body: that's provided later!
|
||||
.headers(requestHeaders.build())
|
||||
.build();
|
||||
Request.Builder builder = new Request.Builder()
|
||||
.url(getURL())
|
||||
.method(method, null /* No body; that's passed separately. */);
|
||||
Headers headers = requestHeaders.build();
|
||||
for (int i = 0; i < headers.length(); i++) {
|
||||
builder.addHeader(headers.getFieldName(i), headers.getValue(i));
|
||||
}
|
||||
Request request = builder.build();
|
||||
|
||||
// If we're currently not using caches, make sure the engine's client doesn't have one.
|
||||
OkHttpClient engineClient = client;
|
||||
|
||||
@@ -26,7 +26,6 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.CacheRequest;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -44,12 +43,12 @@ public final class SpdyTransport implements Transport {
|
||||
}
|
||||
|
||||
@Override public Request prepareRequest(Request request) {
|
||||
Request.Builder builder = request.newBuilder();
|
||||
|
||||
String version = RequestLine.version(httpEngine.connection.getHttpMinorVersion());
|
||||
URL url = request.url();
|
||||
builder.addSpdyRequestHeaders(request.method(), RequestLine.requestPath(url), version,
|
||||
HttpEngine.getHostHeader(url), httpEngine.getRequest().url().getProtocol());
|
||||
Request.Builder builder = request.newBuilder()
|
||||
.header(":method", request.method())
|
||||
.header(":scheme", httpEngine.getRequest().url().getProtocol())
|
||||
.header(":path", RequestLine.requestPath(request.url()))
|
||||
.header(":version", RequestLine.version(httpEngine.connection.getHttpMinorVersion()))
|
||||
.header(":host", HttpEngine.hostHeader(request.url()));
|
||||
|
||||
if (httpEngine.hasRequestBody()) {
|
||||
long fixedContentLength = httpEngine.policy.getFixedContentLength();
|
||||
@@ -145,7 +144,7 @@ public final class SpdyTransport implements Transport {
|
||||
String version = null;
|
||||
|
||||
Headers.Builder headersBuilder = new Headers.Builder();
|
||||
headersBuilder.set(Response.SELECTED_TRANSPORT, "spdy/3");
|
||||
headersBuilder.set(SyntheticHeaders.SELECTED_TRANSPORT, "spdy/3");
|
||||
for (int i = 0; i < nameValueBlock.size(); i += 2) {
|
||||
String name = nameValueBlock.get(i);
|
||||
String values = nameValueBlock.get(i + 1);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.squareup.okhttp.internal.http;
|
||||
|
||||
import com.squareup.okhttp.internal.Platform;
|
||||
|
||||
/** Headers added to the HTTP response for internal use by OkHttp. */
|
||||
public final class SyntheticHeaders {
|
||||
static final String PREFIX = Platform.get().getPrefix();
|
||||
|
||||
/** The local time when the request was sent. */
|
||||
public static final String SENT_MILLIS = PREFIX + "-Sent-Millis";
|
||||
|
||||
/** The local time when the response was received. */
|
||||
public static final String RECEIVED_MILLIS = PREFIX + "-Received-Millis";
|
||||
|
||||
/** The response source. */
|
||||
public static final String RESPONSE_SOURCE = PREFIX + "-Response-Source";
|
||||
|
||||
/** The selected transport (spdy/3, http/1.1, etc). */
|
||||
public static final String SELECTED_TRANSPORT = PREFIX + "-Selected-Transport";
|
||||
|
||||
private SyntheticHeaders() {
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,8 @@ public final class AsyncApiTest {
|
||||
.addHeader("Content-Type: text/plain"));
|
||||
server.play();
|
||||
|
||||
Request request = new Request.Builder(server.getUrl("/"))
|
||||
Request request = new Request.Builder()
|
||||
.url(server.getUrl("/"))
|
||||
.header("User-Agent", "AsyncApiTest")
|
||||
.build();
|
||||
client.enqueue(request, receiver);
|
||||
@@ -80,7 +81,9 @@ public final class AsyncApiTest {
|
||||
client.setSslSocketFactory(sslContext.getSocketFactory());
|
||||
client.setHostnameVerifier(new RecordingHostnameVerifier());
|
||||
|
||||
Request request = new Request.Builder(server.getUrl("/")).build();
|
||||
Request request = new Request.Builder()
|
||||
.url(server.getUrl("/"))
|
||||
.build();
|
||||
client.enqueue(request, receiver);
|
||||
|
||||
receiver.await(request.url()).assertHandshake();
|
||||
@@ -90,7 +93,8 @@ public final class AsyncApiTest {
|
||||
server.enqueue(new MockResponse().setBody("abc"));
|
||||
server.play();
|
||||
|
||||
Request request = new Request.Builder(server.getUrl("/"))
|
||||
Request request = new Request.Builder()
|
||||
.url(server.getUrl("/"))
|
||||
.post(Request.Body.create(MediaType.parse("text/plain"), "def"))
|
||||
.build();
|
||||
client.enqueue(request, receiver);
|
||||
@@ -112,12 +116,16 @@ public final class AsyncApiTest {
|
||||
|
||||
client.setOkResponseCache(cache);
|
||||
|
||||
Request request1 = new Request.Builder(server.getUrl("/")).build();
|
||||
Request request1 = new Request.Builder()
|
||||
.url(server.getUrl("/"))
|
||||
.build();
|
||||
client.enqueue(request1, receiver);
|
||||
receiver.await(request1.url()).assertCode(200).assertBody("A");
|
||||
assertNull(server.takeRequest().getHeader("If-None-Match"));
|
||||
|
||||
Request request2 = new Request.Builder(server.getUrl("/")).build();
|
||||
Request request2 = new Request.Builder()
|
||||
.url(server.getUrl("/"))
|
||||
.build();
|
||||
client.enqueue(request2, receiver);
|
||||
receiver.await(request2.url()).assertCode(200).assertBody("A");
|
||||
assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
|
||||
|
||||
@@ -32,15 +32,15 @@ public final class HeadersTest {
|
||||
"set-cookie", "Cookie1\u0000Cookie2",
|
||||
":status", "200 OK",
|
||||
":version", "HTTP/1.1");
|
||||
Request request = new Request.Builder("http://square.com/").build();
|
||||
Request request = new Request.Builder().url("http://square.com/").build();
|
||||
Response response = SpdyTransport.readNameValueBlock(request, nameValueBlock).build();
|
||||
Headers headers = response.headers();
|
||||
assertEquals(4, headers.length());
|
||||
assertEquals("HTTP/1.1 200 OK", response.statusLine());
|
||||
assertEquals("no-cache, no-store", headers.get("cache-control"));
|
||||
assertEquals("Cookie2", headers.get("set-cookie"));
|
||||
assertEquals("spdy/3", headers.get(Response.SELECTED_TRANSPORT));
|
||||
assertEquals(Response.SELECTED_TRANSPORT, headers.getFieldName(0));
|
||||
assertEquals("spdy/3", headers.get(SyntheticHeaders.SELECTED_TRANSPORT));
|
||||
assertEquals(SyntheticHeaders.SELECTED_TRANSPORT, headers.getFieldName(0));
|
||||
assertEquals("spdy/3", headers.getValue(0));
|
||||
assertEquals("cache-control", headers.getFieldName(1));
|
||||
assertEquals("no-cache, no-store", headers.getValue(1));
|
||||
|
||||
@@ -204,7 +204,7 @@ public final class HttpResponseCacheTest {
|
||||
// exhaust the content stream
|
||||
readAscii(conn);
|
||||
|
||||
Response cached = cache.get(new Request.Builder(url).build());
|
||||
Response cached = cache.get(new Request.Builder().url(url).build());
|
||||
if (shouldPut) {
|
||||
assertNotNull(Integer.toString(responseCode), cached);
|
||||
cached.body().close();
|
||||
@@ -1048,6 +1048,9 @@ public final class HttpResponseCacheTest {
|
||||
HttpURLConnection connection = openConnection(server.getUrl("/"));
|
||||
connection.addRequestProperty("Cache-Control", "only-if-cached");
|
||||
assertGatewayTimeout(connection);
|
||||
assertEquals(1, cache.getRequestCount());
|
||||
assertEquals(0, cache.getNetworkCount());
|
||||
assertEquals(0, cache.getHitCount());
|
||||
}
|
||||
|
||||
@Test public void requestOnlyIfCachedWithFullResponseCached() throws IOException {
|
||||
@@ -1060,6 +1063,9 @@ public final class HttpResponseCacheTest {
|
||||
URLConnection connection = openConnection(server.getUrl("/"));
|
||||
connection.addRequestProperty("Cache-Control", "only-if-cached");
|
||||
assertEquals("A", readAscii(connection));
|
||||
assertEquals(2, cache.getRequestCount());
|
||||
assertEquals(1, cache.getNetworkCount());
|
||||
assertEquals(1, cache.getHitCount());
|
||||
}
|
||||
|
||||
@Test public void requestOnlyIfCachedWithConditionalResponseCached() throws IOException {
|
||||
@@ -1072,6 +1078,9 @@ public final class HttpResponseCacheTest {
|
||||
HttpURLConnection connection = openConnection(server.getUrl("/"));
|
||||
connection.addRequestProperty("Cache-Control", "only-if-cached");
|
||||
assertGatewayTimeout(connection);
|
||||
assertEquals(2, cache.getRequestCount());
|
||||
assertEquals(1, cache.getNetworkCount());
|
||||
assertEquals(0, cache.getHitCount());
|
||||
}
|
||||
|
||||
@Test public void requestOnlyIfCachedWithUnhelpfulResponseCached() throws IOException {
|
||||
@@ -1082,6 +1091,9 @@ public final class HttpResponseCacheTest {
|
||||
HttpURLConnection connection = openConnection(server.getUrl("/"));
|
||||
connection.addRequestProperty("Cache-Control", "only-if-cached");
|
||||
assertGatewayTimeout(connection);
|
||||
assertEquals(2, cache.getRequestCount());
|
||||
assertEquals(1, cache.getNetworkCount());
|
||||
assertEquals(0, cache.getHitCount());
|
||||
}
|
||||
|
||||
@Test public void requestCacheControlNoCache() throws Exception {
|
||||
@@ -1684,8 +1696,8 @@ public final class HttpResponseCacheTest {
|
||||
connection.addRequestProperty("Cache-Control", "only-if-cached");
|
||||
assertEquals("A", readAscii(connection));
|
||||
|
||||
String source = connection.getHeaderField(Response.RESPONSE_SOURCE);
|
||||
assertEquals(ResponseSource.CACHE.toString() + " 200", source);
|
||||
String source = connection.getHeaderField(SyntheticHeaders.RESPONSE_SOURCE);
|
||||
assertEquals(ResponseSource.CACHE + " 200", source);
|
||||
}
|
||||
|
||||
@Test public void responseSourceHeaderConditionalCacheFetched() throws IOException {
|
||||
@@ -1701,8 +1713,8 @@ public final class HttpResponseCacheTest {
|
||||
HttpURLConnection connection = openConnection(server.getUrl("/"));
|
||||
assertEquals("B", readAscii(connection));
|
||||
|
||||
String source = connection.getHeaderField(Response.RESPONSE_SOURCE);
|
||||
assertEquals(ResponseSource.CONDITIONAL_CACHE.toString() + " 200", source);
|
||||
String source = connection.getHeaderField(SyntheticHeaders.RESPONSE_SOURCE);
|
||||
assertEquals(ResponseSource.CONDITIONAL_CACHE + " 200", source);
|
||||
}
|
||||
|
||||
@Test public void responseSourceHeaderConditionalCacheNotFetched() throws IOException {
|
||||
@@ -1716,8 +1728,8 @@ public final class HttpResponseCacheTest {
|
||||
HttpURLConnection connection = openConnection(server.getUrl("/"));
|
||||
assertEquals("A", readAscii(connection));
|
||||
|
||||
String source = connection.getHeaderField(Response.RESPONSE_SOURCE);
|
||||
assertEquals(ResponseSource.CONDITIONAL_CACHE.toString() + " 304", source);
|
||||
String source = connection.getHeaderField(SyntheticHeaders.RESPONSE_SOURCE);
|
||||
assertEquals(ResponseSource.CONDITIONAL_CACHE + " 304", source);
|
||||
}
|
||||
|
||||
@Test public void responseSourceHeaderFetched() throws IOException {
|
||||
@@ -1727,8 +1739,8 @@ public final class HttpResponseCacheTest {
|
||||
URLConnection connection = openConnection(server.getUrl("/"));
|
||||
assertEquals("A", readAscii(connection));
|
||||
|
||||
String source = connection.getHeaderField(Response.RESPONSE_SOURCE);
|
||||
assertEquals(ResponseSource.NETWORK.toString() + " 200", source);
|
||||
String source = connection.getHeaderField(SyntheticHeaders.RESPONSE_SOURCE);
|
||||
assertEquals(ResponseSource.NETWORK + " 200", source);
|
||||
}
|
||||
|
||||
@Test public void emptyResponseHeaderNameFromCacheIsLenient() throws Exception {
|
||||
@@ -1943,6 +1955,8 @@ public final class HttpResponseCacheTest {
|
||||
}
|
||||
assertEquals(504, connection.getResponseCode());
|
||||
assertEquals(-1, connection.getErrorStream().read());
|
||||
assertEquals(ResponseSource.NONE + " 504",
|
||||
connection.getHeaderField(SyntheticHeaders.RESPONSE_SOURCE));
|
||||
}
|
||||
|
||||
enum TransferKind {
|
||||
|
||||
Reference in New Issue
Block a user