diff --git a/.gitignore b/.gitignore index 133735f7b..226a3f3d6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ release.properties .idea *.iml +*.ipr +*.iws classes obj diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java index 34aa9818a..22d4395df 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java @@ -278,7 +278,7 @@ public class HttpEngine { hostnameVerifier = policy.hostnameVerifier; } Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory, - hostnameVerifier, policy.authenticator, policy.requestedProxy, policy.transports); + hostnameVerifier, policy.authenticator, policy.requestedProxy, policy.getTransports()); routeSelector = new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool, Dns.DEFAULT, policy.getFailedRoutes()); } diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java index f6d77b250..f04b31779 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java @@ -127,10 +127,13 @@ public final class HttpTransport implements Transport { } @Override public ResponseHeaders readResponseHeaders() throws IOException { - RawHeaders headers = RawHeaders.fromBytes(socketIn); - httpEngine.connection.setHttpMinorVersion(headers.getHttpMinorVersion()); - httpEngine.receiveHeaders(headers); - return new ResponseHeaders(httpEngine.uri, headers); + RawHeaders rawHeaders = RawHeaders.fromBytes(socketIn); + httpEngine.connection.setHttpMinorVersion(rawHeaders.getHttpMinorVersion()); + httpEngine.receiveHeaders(rawHeaders); + + ResponseHeaders headers = new ResponseHeaders(httpEngine.uri, rawHeaders); + headers.setTransport("http/1.1"); + return headers; } public boolean makeReusable(boolean streamCancelled, OutputStream requestBodyOut, diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java index ee73c7fa3..c7d75a962 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java @@ -40,6 +40,7 @@ import java.net.SocketPermission; import java.net.URL; import java.security.Permission; import java.security.cert.CertificateException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -93,7 +94,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection { /* SSL configuration; necessary for HTTP requests that get redirected to HTTPS. */ SSLSocketFactory sslSocketFactory; HostnameVerifier hostnameVerifier; - List transports; + private List transports; OkAuthenticator authenticator; final Set failedRoutes; @@ -125,6 +126,10 @@ public class HttpURLConnectionImpl extends HttpURLConnection { return failedRoutes; } + List getTransports() { + return transports; + } + @Override public final void connect() throws IOException { initHttpEngine(); boolean success; @@ -547,7 +552,11 @@ public class HttpURLConnectionImpl extends HttpURLConnection { if (field == null) { throw new NullPointerException("field == null"); } - rawRequestHeaders.set(field, newValue); + if ("X-Android-Transports".equals(field)) { + setTransports(newValue, false /* append */); + } else { + rawRequestHeaders.set(field, newValue); + } } @Override public final void addRequestProperty(String field, String value) { @@ -557,6 +566,54 @@ public class HttpURLConnectionImpl extends HttpURLConnection { if (field == null) { throw new NullPointerException("field == null"); } - rawRequestHeaders.add(field, value); + + if ("X-Android-Transports".equals(field)) { + setTransports(value, true /* append */); + } else { + rawRequestHeaders.add(field, value); + } + } + + /* + * Splits and validates a comma-separated string of transports. + * When append == false, we require that the transport list contains "http/1.1". + */ + private void setTransports(String transportsString, boolean append) { + if (transportsString == null) { + throw new NullPointerException("transportsString == null"); + } + + String[] transports = transportsString.split(",", -1); + ArrayList transportsList = new ArrayList(); + if (!append) { + // If we're not appending to the list, we need to make sure + // the list contains "http/1.1". We do this in a separate loop + // to avoid modifying any state before we validate the input. + boolean containsHttp = false; + for (int i = 0; i < transports.length; ++i) { + if ("http/1.1".equals(transports[i])) { + containsHttp = true; + break; + } + } + + if (!containsHttp) { + throw new IllegalArgumentException("Transport list doesn't contain http/1.1"); + } + } else { + transportsList.addAll(this.transports); + } + + for (int i = 0; i < transports.length; ++i) { + if (transports[i].length() == 0) { + throw new IllegalArgumentException("Transport list contains an empty transport"); + } + + if (!transportsList.contains(transports[i])) { + transportsList.add(transports[i]); + } + } + + this.transports = Util.immutableList(transportsList); } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java index 2ab564dcf..97925c2a4 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java @@ -42,6 +42,9 @@ public final class ResponseHeaders { /** HTTP synthetic header with the response source. */ static final String RESPONSE_SOURCE = "X-Android-Response-Source"; + /** HTTP synthetic header with the selected transport (spdy/3, http/1.1, etc.) */ + static final String SELECTED_TRANSPORT = "X-Android-Selected-Transport"; + private final URI uri; private final RawHeaders headers; @@ -278,6 +281,10 @@ public final class ResponseHeaders { 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. diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java index 18ab5668e..73709b581 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java @@ -71,7 +71,10 @@ public final class SpdyTransport implements Transport { RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock); rawHeaders.computeResponseStatusLineFromSpdyHeaders(); httpEngine.receiveHeaders(rawHeaders); - return new ResponseHeaders(httpEngine.uri, rawHeaders); + + ResponseHeaders headers = new ResponseHeaders(httpEngine.uri, rawHeaders); + headers.setTransport("spdy/3"); + return headers; } @Override public InputStream getTransferStream(CacheRequest cacheRequest) throws IOException {