From 9766de54dc8be4560b45d89452b016998277dae3 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Wed, 15 Jan 2014 22:36:51 -0800 Subject: [PATCH] consolidate protocol constants --- .../okhttp/internal/spdy/SpdyServer.java | 3 +- .../okhttp/mockwebserver/MockWebServer.java | 72 ++++-------------- .../java/com/squareup/okhttp/Protocol.java | 75 +++++++++++++++++++ .../squareup/okhttp/internal/ByteString.java | 7 +- .../squareup/okhttp/internal/Platform.java | 59 +++++++++------ .../com/squareup/okhttp/internal/Util.java | 14 +++- .../okhttp/internal/spdy/HpackDraft05.java | 1 + .../okhttp/internal/spdy/Http20Draft09.java | 5 +- .../squareup/okhttp/internal/spdy/Spdy3.java | 5 +- .../okhttp/internal/spdy/SpdyConnection.java | 27 ++++--- .../okhttp/internal/spdy/Variant.java | 5 +- .../okhttp/internal/ByteStringTest.java | 6 -- .../java/com/squareup/okhttp/Address.java | 21 +++--- .../java/com/squareup/okhttp/Connection.java | 64 +++++----------- .../com/squareup/okhttp/OkHttpClient.java | 72 +++++++++++++----- .../java/com/squareup/okhttp/Response.java | 2 +- .../okhttp/internal/http/HttpEngine.java | 2 +- .../okhttp/internal/http/HttpTransport.java | 4 +- .../internal/http/HttpURLConnectionImpl.java | 32 +++++--- .../okhttp/internal/http/OkHeaders.java | 10 ++- .../okhttp/internal/http/SpdyTransport.java | 31 ++++---- .../squareup/okhttp/ConnectionPoolTest.java | 4 +- .../okhttp/internal/http/HeadersTest.java | 46 +++++++----- .../http/HttpOverHttp20Draft09Test.java | 6 +- .../internal/http/HttpOverSpdy3Test.java | 4 +- .../internal/http/HttpOverSpdyTest.java | 16 ++-- .../internal/http/RouteSelectorTest.java | 29 +++---- .../internal/http/URLConnectionTest.java | 13 ++-- 28 files changed, 363 insertions(+), 272 deletions(-) create mode 100644 okhttp-protocols/src/main/java/com/squareup/okhttp/Protocol.java diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/internal/spdy/SpdyServer.java b/mockwebserver/src/main/java/com/squareup/okhttp/internal/spdy/SpdyServer.java index 9b3d4c11d..57ab4917c 100644 --- a/mockwebserver/src/main/java/com/squareup/okhttp/internal/spdy/SpdyServer.java +++ b/mockwebserver/src/main/java/com/squareup/okhttp/internal/spdy/SpdyServer.java @@ -17,6 +17,7 @@ package com.squareup.okhttp.internal.spdy; import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.Protocol; import com.squareup.okhttp.internal.SslContextBuilder; import java.io.File; import java.io.FileInputStream; @@ -71,7 +72,7 @@ public final class SpdyServer implements IncomingStreamHandler { System.out.println("UNSUPPORTED"); } @Override public List protocols() { - return Arrays.asList("spdy/3"); + return Arrays.asList(Protocol.SPDY_3.name.utf8()); } @Override public void protocolSelected(String protocol) { System.out.println("PROTOCOL SELECTED: " + protocol); diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java index 35dac4dcf..95cd2a1b3 100644 --- a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java +++ b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java @@ -20,6 +20,7 @@ package com.squareup.okhttp.mockwebserver; import com.squareup.okhttp.internal.ByteString; import com.squareup.okhttp.internal.NamedRunnable; import com.squareup.okhttp.internal.Platform; +import com.squareup.okhttp.Protocol; import com.squareup.okhttp.internal.Util; import com.squareup.okhttp.internal.spdy.IncomingStreamHandler; import com.squareup.okhttp.internal.spdy.SpdyConnection; @@ -43,7 +44,6 @@ import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -71,29 +71,6 @@ import static com.squareup.okhttp.mockwebserver.SocketPolicy.FAIL_HANDSHAKE; * replays them upon request in sequence. */ public final class MockWebServer { - private static final byte[] HTTP_20_DRAFT_09 = new byte[] { - 'H', 'T', 'T', 'P', '-', 'd', 'r', 'a', 'f', 't', '-', '0', '9', '/', '2', '.', '0' - }; - private static final byte[] SPDY3 = new byte[] { - 's', 'p', 'd', 'y', '/', '3' - }; - private static final byte[] HTTP_11 = new byte[] { - 'h', 't', 't', 'p', '/', '1', '.', '1' - }; - private static final byte[] NPN_PROTOCOLS = joinNpnProtocols(HTTP_20_DRAFT_09, SPDY3, HTTP_11); - - private static byte[] joinNpnProtocols(byte[]... protocols) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - for (byte[] protocol : protocols) { - baos.write(protocol.length); - baos.write(protocol); - } - return baos.toByteArray(); - } catch (IOException e) { - throw new AssertionError(e); - } - } private static final X509TrustManager UNTRUSTED_TRUST_MANAGER = new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) @@ -181,7 +158,7 @@ public final class MockWebServer { /** * Sets whether NPN is used on incoming HTTPS connections to negotiate a - * transport like HTTP/1.1 or SPDY/3. Call this method to disable NPN and + * protocol like HTTP/1.1 or SPDY/3. Call this method to disable NPN and * SPDY. */ public void setNpnEnabled(boolean npnEnabled) { @@ -308,7 +285,7 @@ public final class MockWebServer { } public void processConnection() throws Exception { - Transport transport = Transport.HTTP_11; + Protocol protocol = Protocol.HTTP_11; Socket socket; if (sslSocketFactory != null) { if (tunnelProxy) { @@ -327,39 +304,26 @@ public final class MockWebServer { openClientSockets.put(socket, true); if (npnEnabled) { - Platform.get().setNpnProtocols(sslSocket, NPN_PROTOCOLS); + // TODO: expose means to select which protocols to advertise. + Platform.get().setNpnProtocols(sslSocket, Protocol.HTTP2_SPDY3_AND_HTTP); } sslSocket.startHandshake(); if (npnEnabled) { - byte[] selectedProtocol = Platform.get().getNpnSelectedProtocol(sslSocket); - if (selectedProtocol == null || Arrays.equals(selectedProtocol, HTTP_11)) { - transport = Transport.HTTP_11; - } else if (Arrays.equals(selectedProtocol, HTTP_20_DRAFT_09)) { - transport = Transport.HTTP_20_DRAFT_09; - } else if (Arrays.equals(selectedProtocol, SPDY3)) { - transport = Transport.SPDY_3; - } else { - throw new IllegalStateException( - "Unexpected transport: " + new String(selectedProtocol, Util.US_ASCII)); - } + ByteString selectedProtocol = Platform.get().getNpnSelectedProtocol(sslSocket); + protocol = Protocol.find(selectedProtocol); } openClientSockets.remove(raw); } else { socket = raw; } - if (transport == Transport.HTTP_20_DRAFT_09 || transport == Transport.SPDY_3) { - SpdySocketHandler spdySocketHandler = new SpdySocketHandler(socket, transport); - SpdyConnection.Builder builder = new SpdyConnection.Builder(false, socket) - .handler(spdySocketHandler); - if (transport == Transport.HTTP_20_DRAFT_09) { - builder.http20Draft09(); - } else { - builder.spdy3(); - } - SpdyConnection spdyConnection = builder.build(); + if (protocol.spdyVariant) { + SpdySocketHandler spdySocketHandler = new SpdySocketHandler(socket, protocol); + SpdyConnection spdyConnection = new SpdyConnection.Builder(false, socket) + .protocol(protocol) + .handler(spdySocketHandler).build(); openSpdyConnections.put(spdyConnection, Boolean.TRUE); openClientSockets.remove(socket); spdyConnection.readConnectionHeader(); @@ -628,12 +592,12 @@ public final class MockWebServer { /** Processes HTTP requests layered over SPDY/3. */ private class SpdySocketHandler implements IncomingStreamHandler { private final Socket socket; - private final Transport transport; + private final Protocol protocol; private final AtomicInteger sequenceNumber = new AtomicInteger(); - private SpdySocketHandler(Socket socket, Transport transport) { + private SpdySocketHandler(Socket socket, Protocol protocol) { this.socket = socket; - this.transport = transport; + this.protocol = protocol; } @Override public void receive(SpdyStream stream) throws IOException { @@ -647,7 +611,7 @@ public final class MockWebServer { } writeResponse(stream, response); logger.info("Received request: " + request + " and responded: " + response - + " transport is " + transport); + + " protocol is " + protocol.name.utf8()); } private RecordedRequest readRequest(SpdyStream stream) throws IOException { @@ -724,8 +688,4 @@ public final class MockWebServer { } } } - - enum Transport { - HTTP_11, SPDY_3, HTTP_20_DRAFT_09 - } } diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/Protocol.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/Protocol.java new file mode 100644 index 000000000..f57361ee0 --- /dev/null +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/Protocol.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * 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; + +import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.internal.Util; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * Contains protocols that OkHttp supports + * NPN or + * ALPN selection. + * + *

+ *

Protocol vs Scheme

+ * Despite its name, {@link java.net.URL#getProtocol()} returns the + * {@link java.net.URI#getScheme() scheme} (http, https, etc.) of the URL, not + * the protocol (http/1.1, spdy/3, etc.). OkHttp uses the word protocol to + * indicate how HTTP messages are framed. + */ +public enum Protocol { + HTTP_2("HTTP-draft-09/2.0", true), + SPDY_3("spdy/3", true), + HTTP_11("http/1.1", false); + + public static final List HTTP2_SPDY3_AND_HTTP = + Util.immutableList(Arrays.asList(HTTP_2, SPDY_3, HTTP_11)); + public static final List SPDY3_AND_HTTP11 = + Util.immutableList(Arrays.asList(SPDY_3, HTTP_11)); + public static final List HTTP2_AND_HTTP_11 = + Util.immutableList(Arrays.asList(HTTP_2, HTTP_11)); + + /** Identifier string used in NPN or ALPN selection. */ + public final ByteString name; + + /** + * When true the protocol is binary framed and derived from SPDY. + * + * @see com.squareup.okhttp.internal.spdy.Variant + */ + public final boolean spdyVariant; + + Protocol(String name, boolean spdyVariant) { + this.name = ByteString.encodeUtf8(name); + this.spdyVariant = spdyVariant; + } + + /** + * Returns the protocol matching {@code input} or {@link #HTTP_11} is on + * {@code null}. Throws an {@link IOException} when {@code input} doesn't + * match the {@link #name} of a supported protocol. + */ + public static Protocol find(ByteString input) throws IOException { + if (input == null) return HTTP_11; + for (Protocol protocol : values()) { + if (protocol.name.equals(input)) return protocol; + } + throw new IOException("Unexpected protocol: " + input.utf8()); + } +} diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/ByteString.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/ByteString.java index b6a688e3d..51abec5e3 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/ByteString.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/ByteString.java @@ -115,7 +115,7 @@ public final class ByteString { System.arraycopy(byteString.data, 0, result, pos, byteString.size()); pos += byteString.size(); } - return ByteString.of(result); + return new ByteString(result); } private ByteString(byte[] data) { @@ -141,11 +141,6 @@ public final class ByteString { out.write(data); } - /** Writes a subsequence of this byte string to {@code out}. */ - public void write(OutputStream out, int offset, int count) throws IOException { - out.write(data, offset, count); - } - @Override public boolean equals(Object o) { return o == this || o instanceof ByteString && Arrays.equals(((ByteString) o).data, data); } diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java index 33c120d16..9356cdffd 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java @@ -16,9 +16,9 @@ */ package com.squareup.okhttp.internal; +import com.squareup.okhttp.Protocol; import java.io.IOException; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; @@ -106,7 +106,7 @@ public class Platform { } /** Returns the negotiated protocol, or null if no protocol was negotiated. */ - public byte[] getNpnSelectedProtocol(SSLSocket socket) { + public ByteString getNpnSelectedProtocol(SSLSocket socket) { return null; } @@ -114,7 +114,7 @@ public class Platform { * Sets client-supported protocols on a socket to send to a server. The * protocols are only sent if the socket implementation supports NPN. */ - public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) { + public void setNpnProtocols(SSLSocket socket, List npnProtocols) { } public void connectSocket(Socket socket, InetSocketAddress address, @@ -267,11 +267,11 @@ public class Platform { } } - @Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) { + @Override public void setNpnProtocols(SSLSocket socket, List npnProtocols) { if (setNpnProtocols == null) return; if (!openSslSocketClass.isInstance(socket)) return; try { - Object[] parameters = { npnProtocols }; + Object[] parameters = { concatLengthPrefixed(npnProtocols) }; if (setAlpnProtocols != null) { setAlpnProtocols.invoke(socket, parameters); } @@ -283,16 +283,16 @@ public class Platform { } } - @Override public byte[] getNpnSelectedProtocol(SSLSocket socket) { + @Override public ByteString getNpnSelectedProtocol(SSLSocket socket) { if (getNpnSelectedProtocol == null) return null; if (!openSslSocketClass.isInstance(socket)) return null; try { if (getAlpnSelectedProtocol != null) { // Prefer ALPN's result if it is present. byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invoke(socket); - if (alpnResult != null) return alpnResult; + if (alpnResult != null) return ByteString.of(alpnResult); } - return (byte[]) getNpnSelectedProtocol.invoke(socket); + return ByteString.of((byte[]) getNpnSelectedProtocol.invoke(socket)); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { @@ -316,20 +316,15 @@ public class Platform { this.serverProviderClass = serverProviderClass; } - @Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) { + @Override public void setNpnProtocols(SSLSocket socket, List npnProtocols) { try { - List strings = new ArrayList(); - for (int i = 0; i < npnProtocols.length; ) { - int length = npnProtocols[i++]; - strings.add(new String(npnProtocols, i, length, "US-ASCII")); - i += length; + List names = new ArrayList(npnProtocols.size()); + for (int i = 0, size = npnProtocols.size(); i < size; i++) { + names.add(npnProtocols.get(i).name.utf8()); } Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(), - new Class[] {clientProviderClass, serverProviderClass}, - new JettyNpnProvider(strings)); + new Class[] { clientProviderClass, serverProviderClass }, new JettyNpnProvider(names)); putMethod.invoke(null, socket, provider); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); } catch (InvocationTargetException e) { throw new AssertionError(e); } catch (IllegalAccessException e) { @@ -337,7 +332,7 @@ public class Platform { } } - @Override public byte[] getNpnSelectedProtocol(SSLSocket socket) { + @Override public ByteString getNpnSelectedProtocol(SSLSocket socket) { try { JettyNpnProvider provider = (JettyNpnProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket)); @@ -347,9 +342,7 @@ public class Platform { "NPN callback dropped so SPDY is disabled. " + "Is npn-boot on the boot class path?"); return null; } - return provider.unsupported ? null : provider.selected.getBytes("US-ASCII"); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(); + return provider.unsupported ? null : ByteString.encodeUtf8(provider.selected); } catch (InvocationTargetException e) { throw new AssertionError(); } catch (IllegalAccessException e) { @@ -400,4 +393,26 @@ public class Platform { } } } + + /** + * Concatenation of 8-bit, length prefixed protocol names. + * + * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 + */ + static byte[] concatLengthPrefixed(List protocols) { + int size = 0; + for (Protocol protocol : protocols) { + size += protocol.name.size() + 1; // add a byte for 8-bit length prefix. + } + byte[] result = new byte[size]; + int pos = 0; + for (Protocol protocol : protocols) { + int nameSize = protocol.name.size(); + result[pos++] = (byte) nameSize; + // toByteArray allocates an array, but this is only called on new connections. + System.arraycopy(protocol.name.toByteArray(), 0, result, pos, nameSize); + pos += nameSize; + } + return result; + } } diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java index 93fc2f03e..ed325ba07 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java @@ -400,11 +400,19 @@ public final class Util { }; } - public static List byteStringList(String... strings) { - List result = new ArrayList(strings.length); - for (String string : strings) { + public static List byteStringList(String... elements) { + List result = new ArrayList(elements.length); + for (String string : elements) { result.add(ByteString.encodeUtf8(string)); } return result; } + + public static List byteStringList(List elements) { + List result = new ArrayList(elements.size()); + for (int i = 0, size = elements.size(); i < size; i++) { + result.add(ByteString.encodeUtf8(elements.get(i))); + } + return result; + } } diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java index 63a504624..482c286dd 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java @@ -405,6 +405,7 @@ final class HpackDraft05 { } } + // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-4.1.1 public void writeInt(int value, int prefixMask, int bits) throws IOException { // Write the raw value for a single byte value. if (value < prefixMask) { diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Http20Draft09.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Http20Draft09.java index b12433868..eb03cc389 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Http20Draft09.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Http20Draft09.java @@ -16,6 +16,7 @@ package com.squareup.okhttp.internal.spdy; import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.Protocol; import com.squareup.okhttp.internal.Util; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; @@ -33,8 +34,8 @@ import java.util.List; */ public final class Http20Draft09 implements Variant { - @Override public String getProtocol() { - return "HTTP-draft-09/2.0"; + @Override public Protocol getProtocol() { + return Protocol.HTTP_2; } private static final byte[] CONNECTION_HEADER; diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java index 595ea7b78..fbfd4d469 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java @@ -17,6 +17,7 @@ package com.squareup.okhttp.internal.spdy; import com.squareup.okhttp.internal.ByteString; import com.squareup.okhttp.internal.Platform; +import com.squareup.okhttp.Protocol; import com.squareup.okhttp.internal.Util; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; @@ -31,8 +32,8 @@ import java.util.zip.Deflater; final class Spdy3 implements Variant { - @Override public String getProtocol() { - return "spdy/3"; + @Override public Protocol getProtocol() { + return Protocol.SPDY_3; } static final int TYPE_DATA = 0x0; diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java index aefa0819f..bbacaab9e 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java @@ -18,6 +18,7 @@ package com.squareup.okhttp.internal.spdy; import com.squareup.okhttp.internal.ByteString; import com.squareup.okhttp.internal.NamedRunnable; +import com.squareup.okhttp.Protocol; import com.squareup.okhttp.internal.Util; import java.io.Closeable; import java.io.IOException; @@ -60,7 +61,7 @@ public final class SpdyConnection implements Closeable { Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp SpdyConnection", true)); - /** The protocol variant, like SPDY/3 or HTTP-draft-09/2.0. */ + /** The protocol variant, like {@link com.squareup.okhttp.internal.spdy.Spdy3}. */ final Variant variant; /** True if this peer initiated the connection. */ @@ -104,12 +105,8 @@ public final class SpdyConnection implements Closeable { new Thread(new Reader()).start(); // Not a daemon thread. } - /** - * The protocol name, like {@code spdy/3} or {@code HTTP-draft-09/2.0}. - * - * @see com.squareup.okhttp.internal.spdy.Variant#getProtocol() - */ - public String getProtocol() { + /** The protocol as selected using NPN or ALPN. */ + public Protocol getProtocol() { return variant.getProtocol(); } @@ -423,13 +420,15 @@ public final class SpdyConnection implements Closeable { return this; } - public Builder spdy3() { - this.variant = Variant.SPDY3; - return this; - } - - public Builder http20Draft09() { - this.variant = Variant.HTTP_20_DRAFT_09; + public Builder protocol(Protocol protocol) { + // TODO: protocol == variant.getProtocol, so we could map this. + if (protocol == Protocol.HTTP_2) { + this.variant = Variant.HTTP_20_DRAFT_09; + } else if (protocol == Protocol.SPDY_3) { + this.variant = Variant.SPDY3; + } else { + throw new AssertionError(protocol); + } return this; } diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Variant.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Variant.java index 3ff3e0b2f..b31d68613 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Variant.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Variant.java @@ -15,6 +15,7 @@ */ package com.squareup.okhttp.internal.spdy; +import com.squareup.okhttp.Protocol; import java.io.InputStream; import java.io.OutputStream; @@ -23,8 +24,8 @@ interface Variant { Variant SPDY3 = new Spdy3(); Variant HTTP_20_DRAFT_09 = new Http20Draft09(); - /** The protocol name, like {@code spdy/3} or {@code HTTP-draft-09/2.0}. */ - String getProtocol(); + /** The protocol as selected using NPN or ALPN. */ + Protocol getProtocol(); /** * @param client true if this is the HTTP client's reader, reading frames from diff --git a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/ByteStringTest.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/ByteStringTest.java index 95b91d176..e5468894b 100644 --- a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/ByteStringTest.java +++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/ByteStringTest.java @@ -66,12 +66,6 @@ public class ByteStringTest { assertByteArraysEquals(new byte[] { 0x61, 0x62, 0x63 }, out.toByteArray()); } - @Test public void writeWithOffset() throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ByteString.of((byte) 0x61, (byte) 0x62, (byte) 0x63).write(out, 1, 2); - assertByteArraysEquals(new byte[] { 0x62, 0x63 }, out.toByteArray()); - } - @Test public void concat() { assertEquals(ByteString.of(), ByteString.concat()); assertEquals(ByteString.of(), ByteString.concat(ByteString.EMPTY)); diff --git a/okhttp/src/main/java/com/squareup/okhttp/Address.java b/okhttp/src/main/java/com/squareup/okhttp/Address.java index b34bd9128..ebda2a64a 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Address.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Address.java @@ -41,22 +41,22 @@ public final class Address { final SSLSocketFactory sslSocketFactory; final HostnameVerifier hostnameVerifier; final OkAuthenticator authenticator; - final List transports; + final List protocols; public Address(String uriHost, int uriPort, SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier, OkAuthenticator authenticator, Proxy proxy, - List transports) throws UnknownHostException { + List protocols) throws UnknownHostException { if (uriHost == null) throw new NullPointerException("uriHost == null"); if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort); if (authenticator == null) throw new IllegalArgumentException("authenticator == null"); - if (transports == null) throw new IllegalArgumentException("transports == null"); + if (protocols == null) throw new IllegalArgumentException("protocols == null"); this.proxy = proxy; this.uriHost = uriHost; this.uriPort = uriPort; this.sslSocketFactory = sslSocketFactory; this.hostnameVerifier = hostnameVerifier; this.authenticator = authenticator; - this.transports = Util.immutableList(transports); + this.protocols = Util.immutableList(protocols); } /** Returns the hostname of the origin server. */ @@ -97,11 +97,12 @@ public final class Address { } /** - * Returns the client's transports. This method always returns a non-null list - * that contains "http/1.1", possibly among other transports. + * Returns the protocols the client supports. This method always returns a + * non-null list that contains minimally + * {@link Protocol#HTTP_11}. */ - public List getTransports() { - return transports; + public List getProtocols() { + return protocols; } /** @@ -121,7 +122,7 @@ public final class Address { && equal(this.sslSocketFactory, that.sslSocketFactory) && equal(this.hostnameVerifier, that.hostnameVerifier) && equal(this.authenticator, that.authenticator) - && equal(this.transports, that.transports); + && equal(this.protocols, that.protocols); } return false; } @@ -134,7 +135,7 @@ public final class Address { result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0); result = 31 * result + (authenticator != null ? authenticator.hashCode() : 0); result = 31 * result + (proxy != null ? proxy.hashCode() : 0); - result = 31 * result + transports.hashCode(); + result = 31 * result + protocols.hashCode(); return result; } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/Connection.java b/okhttp/src/main/java/com/squareup/okhttp/Connection.java index 9a7f1b0f9..9ff7b7069 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Connection.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Connection.java @@ -16,6 +16,7 @@ */ package com.squareup.okhttp; +import com.squareup.okhttp.internal.ByteString; import com.squareup.okhttp.internal.Platform; import com.squareup.okhttp.internal.http.HttpAuthenticator; import com.squareup.okhttp.internal.http.HttpEngine; @@ -31,7 +32,6 @@ import java.io.OutputStream; import java.net.Proxy; import java.net.Socket; import java.net.SocketTimeoutException; -import java.util.Arrays; import javax.net.ssl.SSLSocket; import static java.net.HttpURLConnection.HTTP_OK; @@ -56,7 +56,7 @@ import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; *
  • Server Name Indication (SNI) enables one IP address to negotiate secure * connections for multiple domain names. *
  • Next Protocol Negotiation (NPN) enables the HTTPS port (443) to be used - * for both HTTP and SPDY transports. + * for both HTTP and SPDY protocols. * * Unfortunately, older HTTPS servers refuse to connect when such options are * presented. Rather than avoiding these options entirely, this class allows a @@ -64,31 +64,6 @@ import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; * should the attempt fail. */ public final class Connection implements Closeable { - private static final byte[] ALL_PROTOCOLS = new byte[] { - 17, 'H', 'T', 'T', 'P', '-', 'd', 'r', 'a', 'f', 't', '-', '0', '9', '/', '2', '.', '0', - 6, 's', 'p', 'd', 'y', '/', '3', - 8, 'h', 't', 't', 'p', '/', '1', '.', '1' - }; - - private static final byte[] SPDY3_AND_HTTP11 = new byte[] { - 6, 's', 'p', 'd', 'y', '/', '3', - 8, 'h', 't', 't', 'p', '/', '1', '.', '1' - }; - - private static final byte[] HTTP2_AND_HTTP = new byte[] { - 17, 'H', 'T', 'T', 'P', '-', 'd', 'r', 'a', 'f', 't', '-', '0', '9', '/', '2', '.', '0', - 8, 'h', 't', 't', 'p', '/', '1', '.', '1' - }; - - private static final byte[] HTTP_20_DRAFT_09 = new byte[] { - 'H', 'T', 'T', 'P', '-', 'd', 'r', 'a', 'f', 't', '-', '0', '9', '/', '2', '.', '0' - }; - private static final byte[] SPDY3 = new byte[] { - 's', 'p', 'd', 'y', '/', '3' - }; - private static final byte[] HTTP_11 = new byte[] { - 'h', 't', 't', 'p', '/', '1', '.', '1' - }; private final Route route; @@ -145,19 +120,19 @@ public final class Connection implements Closeable { platform.supportTlsIntolerantServer(sslSocket); } - boolean useNpn = route.modernTls && ( - route.address.transports.contains("HTTP-draft-09/2.0") - || route.address.transports.contains("spdy/3") + boolean useNpn = route.modernTls && (// Contains a spdy variant. + route.address.protocols.contains(Protocol.HTTP_2) + || route.address.protocols.contains(Protocol.SPDY_3) ); if (useNpn) { - if (route.address.transports.contains("HTTP-draft-09/2.0") - && route.address.transports.contains("spdy/3")) { - platform.setNpnProtocols(sslSocket, ALL_PROTOCOLS); - } else if (route.address.transports.contains("HTTP-draft-09/2.0")) { - platform.setNpnProtocols(sslSocket, HTTP2_AND_HTTP); + if (route.address.protocols.contains(Protocol.HTTP_2) // Contains both spdy variants. + && route.address.protocols.contains(Protocol.SPDY_3)) { + platform.setNpnProtocols(sslSocket, Protocol.HTTP2_SPDY3_AND_HTTP); + } else if (route.address.protocols.contains(Protocol.HTTP_2)) { + platform.setNpnProtocols(sslSocket, Protocol.HTTP2_AND_HTTP_11); } else { - platform.setNpnProtocols(sslSocket, SPDY3_AND_HTTP11); + platform.setNpnProtocols(sslSocket, Protocol.SPDY3_AND_HTTP11); } } @@ -174,19 +149,14 @@ public final class Connection implements Closeable { handshake = Handshake.get(sslSocket.getSession()); streamWrapper(); - byte[] selectedProtocol; - if (useNpn && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) { - if (Arrays.equals(selectedProtocol, HTTP_20_DRAFT_09) - || Arrays.equals(selectedProtocol, SPDY3)) { - SpdyConnection.Builder builder = - new SpdyConnection.Builder(route.address.getUriHost(), true, in, out); - if (Arrays.equals(selectedProtocol, HTTP_20_DRAFT_09)) builder.http20Draft09(); + ByteString maybeProtocol; + if (useNpn && (maybeProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) { + Protocol selectedProtocol = Protocol.find(maybeProtocol); // Throws IOE on unknown. + if (selectedProtocol.spdyVariant) { sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream. - spdyConnection = builder.build(); + spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out) + .protocol(selectedProtocol).build(); spdyConnection.sendConnectionHeader(); - } else if (!Arrays.equals(selectedProtocol, HTTP_11)) { - throw new IOException( - "Unexpected NPN transport " + new String(selectedProtocol, "ISO-8859-1")); } } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java index ef934328d..576599762 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java +++ b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java @@ -15,11 +15,13 @@ */ package com.squareup.okhttp; +import com.squareup.okhttp.internal.ByteString; import com.squareup.okhttp.internal.Util; import com.squareup.okhttp.internal.http.HttpAuthenticator; import com.squareup.okhttp.internal.http.HttpURLConnectionImpl; import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl; import com.squareup.okhttp.internal.tls.OkHostnameVerifier; +import java.io.IOException; import java.net.CookieHandler; import java.net.HttpURLConnection; import java.net.Proxy; @@ -29,7 +31,7 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import javax.net.ssl.HostnameVerifier; @@ -38,13 +40,11 @@ import javax.net.ssl.SSLSocketFactory; /** Configures and creates HTTP connections. */ public final class OkHttpClient implements URLStreamHandlerFactory, Cloneable { - private static final List DEFAULT_TRANSPORTS - = Util.immutableList(Arrays.asList("HTTP-draft-09/2.0", "spdy/3", "http/1.1")); private final RouteDatabase routeDatabase; private Dispatcher dispatcher; private Proxy proxy; - private List transports; + private List protocols; private ProxySelector proxySelector; private CookieHandler cookieHandler; private OkResponseCache responseCache; @@ -296,20 +296,39 @@ public final class OkHttpClient implements URLStreamHandlerFactory, Cloneable { } /** - * Configure the transports used by this client to communicate with remote + * @deprecated OkHttp 2 enforces an enumeration of {@link Protocol protocols} + * that can be selected. Please switch to {@link #setProtocols(java.util.List)}. + */ + @Deprecated + public OkHttpClient setTransports(List transports) { + List protocols = new ArrayList(transports.size()); + for (int i = 0, size = transports.size(); i < size; i++) { + try { + Protocol protocol = Protocol.find(ByteString.encodeUtf8(transports.get(i))); + protocols.add(protocol); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + return setProtocols(protocols); + } + + /** + * Configure the protocols used by this client to communicate with remote * servers. By default this client will prefer the most efficient transport - * available, falling back to more ubiquitous transports. Applications should + * available, falling back to more ubiquitous protocols. Applications should * only call this method to avoid specific compatibility problems, such as web * servers that behave incorrectly when SPDY is enabled. * - *

    The following transports are currently supported: + *

    The following protocols are currently supported: *

    * *

    This is an evolving set. Future releases may drop - * support for transitional transports (like spdy/3), in favor of their + * support for transitional protocols (like spdy/3), in favor of their * successors (spdy/4 or http/2.0). The http/1.1 transport will never be * dropped. * @@ -319,28 +338,41 @@ public final class OkHttpClient implements URLStreamHandlerFactory, Cloneable { * (such as ALPN) * to negotiate a transport. * - * @param transports the transports to use, in order of preference. The list + * @param protocols the protocols to use, in order of preference. The list * must contain "http/1.1". It must not contain null. */ - public OkHttpClient setTransports(List transports) { - transports = Util.immutableList(transports); - if (!transports.contains("http/1.1")) { - throw new IllegalArgumentException("transports doesn't contain http/1.1: " + transports); + public OkHttpClient setProtocols(List protocols) { + protocols = Util.immutableList(protocols); + if (!protocols.contains(Protocol.HTTP_11)) { + throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols); } - if (transports.contains(null)) { - throw new IllegalArgumentException("transports must not contain null"); + if (protocols.contains(null)) { + throw new IllegalArgumentException("protocols must not contain null"); } - if (transports.contains("")) { - throw new IllegalArgumentException("transports contains an empty string"); + if (protocols.contains(ByteString.EMPTY)) { + throw new IllegalArgumentException("protocols contains an empty string"); } - this.transports = transports; + this.protocols = Util.immutableList(protocols); return this; } + /** + * @deprecated OkHttp 2 enforces an enumeration of {@link Protocol protocols} + * that can be selected. Please switch to {@link #getProtocols()}. + */ + @Deprecated public List getTransports() { + List transports = new ArrayList(protocols.size()); + for (int i = 0, size = protocols.size(); i < size; i++) { + transports.add(protocols.get(i).name.utf8()); + } return transports; } + public List getProtocols() { + return protocols; + } + /** * Schedules {@code request} to be executed at some point in the future. The * {@link #getDispatcher dispatcher} defines when the request will run: @@ -404,8 +436,8 @@ public final class OkHttpClient implements URLStreamHandlerFactory, Cloneable { if (result.connectionPool == null) { result.connectionPool = ConnectionPool.getDefault(); } - if (result.transports == null) { - result.transports = DEFAULT_TRANSPORTS; + if (result.protocols == null) { + result.protocols = Protocol.HTTP2_SPDY3_AND_HTTP; } return result; } diff --git a/okhttp/src/main/java/com/squareup/okhttp/Response.java b/okhttp/src/main/java/com/squareup/okhttp/Response.java index 0110f3e19..953173257 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Response.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Response.java @@ -218,7 +218,7 @@ public final class Response { /** * Returns true if further data from this response body should be read at - * this time. For asynchronous transports like SPDY and HTTP/2.0, this will + * this time. For asynchronous protocols like SPDY and HTTP/2.0, this will * return false once all locally-available body bytes have been read. * *

    Clients with many concurrent downloads can use this method to reduce 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 78a4e9de3..6c954128f 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 @@ -234,7 +234,7 @@ public class HttpEngine { hostnameVerifier = client.getHostnameVerifier(); } Address address = new Address(uriHost, getEffectivePort(request.url()), sslSocketFactory, - hostnameVerifier, client.getAuthenticator(), client.getProxy(), client.getTransports()); + hostnameVerifier, client.getAuthenticator(), client.getProxy(), client.getProtocols()); routeSelector = new RouteSelector(address, request.uri(), client.getProxySelector(), client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase()); } 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 d2fe8910c..c516b4770 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 @@ -21,6 +21,7 @@ import com.squareup.okhttp.Headers; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; import com.squareup.okhttp.internal.AbstractOutputStream; +import com.squareup.okhttp.Protocol; import com.squareup.okhttp.internal.Util; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -155,7 +156,8 @@ public final class HttpTransport implements Transport { Response.Builder responseBuilder = new Response.Builder() .statusLine(statusLine) - .header(OkHeaders.SELECTED_TRANSPORT, "http/1.1"); + .header(OkHeaders.SELECTED_TRANSPORT, Protocol.HTTP_11.name.utf8()) + .header(OkHeaders.SELECTED_PROTOCOL, Protocol.HTTP_11.name.utf8()); Headers.Builder headersBuilder = new Headers.Builder(); OkHeaders.readHeaders(headersBuilder, in); 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 246d2710f..ae01d750e 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 @@ -23,7 +23,9 @@ import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; import com.squareup.okhttp.Route; +import com.squareup.okhttp.internal.ByteString; import com.squareup.okhttp.internal.Platform; +import com.squareup.okhttp.Protocol; import com.squareup.okhttp.internal.Util; import java.io.FileNotFoundException; import java.io.IOException; @@ -502,8 +504,9 @@ public class HttpURLConnectionImpl extends HttpURLConnection { return; } - if ("X-Android-Transports".equals(field)) { - setTransports(newValue, false /* append */); + // TODO: Deprecate use of X-Android-Transports header? + if ("X-Android-Transports".equals(field) || "X-Android-Protocols".equals(field)) { + setProtocols(newValue, false /* append */); } else { requestHeaders.set(field, newValue); } @@ -535,26 +538,33 @@ public class HttpURLConnectionImpl extends HttpURLConnection { return; } - if ("X-Android-Transports".equals(field)) { - setTransports(value, true /* append */); + // TODO: Deprecate use of X-Android-Transports header? + if ("X-Android-Transports".equals(field) || "X-Android-Protocols".equals(field)) { + setProtocols(value, true /* append */); } else { requestHeaders.add(field, value); } } /* - * Splits and validates a comma-separated string of transports. + * Splits and validates a comma-separated string of protocols. * When append == false, we require that the transport list contains "http/1.1". + * Throws {@link IllegalStateException} when one of the protocols isn't + * defined in {@link Protocol OkHttp's protocol enumeration}. */ - private void setTransports(String transportsString, boolean append) { - List transportsList = new ArrayList(); + private void setProtocols(String protocolsString, boolean append) { + List protocolsList = new ArrayList(); if (append) { - transportsList.addAll(client.getTransports()); + protocolsList.addAll(client.getProtocols()); } - for (String transport : transportsString.split(",", -1)) { - transportsList.add(transport); + for (ByteString protocol : Util.byteStringList(protocolsString.split(",", -1))) { + try { + protocolsList.add(Protocol.find(protocol)); + } catch (IOException e) { + throw new IllegalStateException(e); + } } - client.setTransports(transportsList); + client.setProtocols(protocolsList); } @Override public void setFixedLengthStreamingMode(int contentLength) { diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/OkHeaders.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/OkHeaders.java index 09193537c..bc18ea6ed 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/OkHeaders.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/OkHeaders.java @@ -50,10 +50,18 @@ public final class OkHeaders { public static final String RESPONSE_SOURCE = PREFIX + "-Response-Source"; /** - * Synthetic response header: the selected transport ("spdy/3", "http/1.1", etc). + * @deprecated OkHttp 2 enforces an enumeration of {@link com.squareup.okhttp.Protocol protocols} + * that can be selected. Please use #SELECTED_PROTOCOL */ + @Deprecated public static final String SELECTED_TRANSPORT = PREFIX + "-Selected-Transport"; + /** + * Synthetic response header: the selected + * {@link com.squareup.okhttp.Protocol protocol} ("spdy/3", "http/1.1", etc). + */ + public static final String SELECTED_PROTOCOL = PREFIX + "-Selected-Protocol"; + private OkHeaders() { } 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 07bb90e79..4c1e80457 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 @@ -20,6 +20,7 @@ import com.squareup.okhttp.Headers; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.Protocol; import com.squareup.okhttp.internal.spdy.ErrorCode; import com.squareup.okhttp.internal.spdy.SpdyConnection; import com.squareup.okhttp.internal.spdy.SpdyStream; @@ -41,7 +42,6 @@ public final class SpdyTransport implements Transport { private static final ByteString HEADER_HOST = ByteString.encodeUtf8(":host"); private static final ByteString HEADER_AUTHORITY = ByteString.encodeUtf8(":authority"); private static final ByteString HEADER_SCHEME = ByteString.encodeUtf8(":scheme"); - private static final ByteString NULL = ByteString.of((byte) 0x00); private final HttpEngine httpEngine; private final SpdyConnection spdyConnection; @@ -88,7 +88,7 @@ public final class SpdyTransport implements Transport { * Names are all lower case. No names are repeated. If any name has multiple * values, they are concatenated using "\0" as a delimiter. */ - public static List writeNameValueBlock(Request request, String protocol, + public static List writeNameValueBlock(Request request, Protocol protocol, String version) { Headers headers = request.headers(); // TODO: make the known header names constants. @@ -99,9 +99,9 @@ public final class SpdyTransport implements Transport { result.add(ByteString.encodeUtf8(RequestLine.requestPath(request.url()))); result.add(HEADER_VERSION); result.add(ByteString.encodeUtf8(version)); - if (protocol.equals("spdy/3")) { + if (Protocol.SPDY_3 == protocol) { result.add(HEADER_HOST); - } else if (protocol.equals("HTTP-draft-09/2.0")) { + } else if (Protocol.HTTP_2 == protocol) { result.add(HEADER_AUTHORITY); } else { throw new AssertionError(); @@ -127,19 +127,19 @@ public final class SpdyTransport implements Transport { || name.equals(":scheme")) { continue; } - ByteString valueBytes = ByteString.encodeUtf8(value); // If we haven't seen this name before, add the pair to the end of the list... if (names.add(ByteString.encodeUtf8(name))) { result.add(ByteString.encodeUtf8(name)); - result.add(valueBytes); + result.add(ByteString.encodeUtf8(value)); continue; } // ...otherwise concatenate the existing values and this value. for (int j = 0; j < result.size(); j += 2) { if (result.get(j).utf8Equals(name)) { - result.set(j + 1, ByteString.concat(result.get(j + 1), NULL, valueBytes)); + String concatenated = joinOnNull(result.get(j + 1).utf8(), value); + result.set(j + 1, ByteString.encodeUtf8(concatenated)); break; } } @@ -147,9 +147,13 @@ public final class SpdyTransport implements Transport { return result; } + private static String joinOnNull(String first, String second) { + return new StringBuilder(first).append('\0').append(second).toString(); + } + /** Returns headers for a name value block containing a SPDY response. */ public static Response.Builder readNameValueBlock(List nameValueBlock, - String protocol) throws IOException { + Protocol protocol) throws IOException { if (nameValueBlock.size() % 2 != 0) { throw new IllegalArgumentException("Unexpected name value block: " + nameValueBlock); } @@ -157,7 +161,8 @@ public final class SpdyTransport implements Transport { String version = "HTTP/1.1"; // TODO: why are we expecting :version? Headers.Builder headersBuilder = new Headers.Builder(); - headersBuilder.set(OkHeaders.SELECTED_TRANSPORT, protocol); + headersBuilder.set(OkHeaders.SELECTED_TRANSPORT, protocol.name.utf8()); + headersBuilder.set(OkHeaders.SELECTED_PROTOCOL, protocol.name.utf8()); for (int i = 0; i < nameValueBlock.size(); i += 2) { String name = nameValueBlock.get(i).utf8(); String values = nameValueBlock.get(i + 1).utf8(); @@ -206,9 +211,9 @@ public final class SpdyTransport implements Transport { } /** When true, this header should not be emitted or consumed. */ - private static boolean isProhibitedHeader(String protocol, String name) { + private static boolean isProhibitedHeader(Protocol protocol, String name) { boolean prohibited = false; - if (protocol.equals("spdy/3")) { + if (protocol == Protocol.SPDY_3) { // http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3#TOC-3.2.1-Request if (name.equals("connection") || name.equals("host") @@ -217,7 +222,7 @@ public final class SpdyTransport implements Transport { || name.equals("transfer-encoding")) { prohibited = true; } - } else if (protocol.equals("HTTP-draft-09/2.0")) { + } else if (protocol == Protocol.HTTP_2) { // http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3 if (name.equals("connection") || name.equals("host") @@ -230,7 +235,7 @@ public final class SpdyTransport implements Transport { prohibited = true; } } else { - throw new AssertionError(); + throw new AssertionError(protocol); } return prohibited; } diff --git a/okhttp/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java b/okhttp/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java index bebe7220b..c02be45cf 100644 --- a/okhttp/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java +++ b/okhttp/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java @@ -61,14 +61,14 @@ public final class ConnectionPoolTest { httpServer.play(); httpAddress = new Address(httpServer.getHostName(), httpServer.getPort(), null, null, - HttpAuthenticator.SYSTEM_DEFAULT, null, Arrays.asList("spdy/3", "http/1.1")); + HttpAuthenticator.SYSTEM_DEFAULT, null, Protocol.SPDY3_AND_HTTP11); httpSocketAddress = new InetSocketAddress(InetAddress.getByName(httpServer.getHostName()), httpServer.getPort()); spdyServer.play(); spdyAddress = new Address(spdyServer.getHostName(), spdyServer.getPort(), sslContext.getSocketFactory(), new RecordingHostnameVerifier(), - HttpAuthenticator.SYSTEM_DEFAULT, null, Arrays.asList("spdy/3", "http/1.1")); + HttpAuthenticator.SYSTEM_DEFAULT, null,Protocol.SPDY3_AND_HTTP11); spdySocketAddress = new InetSocketAddress(InetAddress.getByName(spdyServer.getHostName()), spdyServer.getPort()); diff --git a/okhttp/src/test/java/com/squareup/okhttp/internal/http/HeadersTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HeadersTest.java index 238a199a0..91b3d1cec 100644 --- a/okhttp/src/test/java/com/squareup/okhttp/internal/http/HeadersTest.java +++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HeadersTest.java @@ -19,6 +19,7 @@ import com.squareup.okhttp.Headers; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.Protocol; import java.io.IOException; import java.util.List; import org.junit.Test; @@ -36,21 +37,23 @@ public final class HeadersTest { ":version", "HTTP/1.1"); Request request = new Request.Builder().url("http://square.com/").build(); Response response = - SpdyTransport.readNameValueBlock(nameValueBlock, "spdy/3").request(request).build(); + SpdyTransport.readNameValueBlock(nameValueBlock, Protocol.SPDY_3).request(request).build(); Headers headers = response.headers(); - assertEquals(4, headers.size()); + assertEquals(5, headers.size()); 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(OkHeaders.SELECTED_TRANSPORT)); + assertEquals(Protocol.SPDY_3.name.utf8(), headers.get(OkHeaders.SELECTED_TRANSPORT)); assertEquals(OkHeaders.SELECTED_TRANSPORT, headers.name(0)); - assertEquals("spdy/3", headers.value(0)); - assertEquals("cache-control", headers.name(1)); - assertEquals("no-cache, no-store", headers.value(1)); - assertEquals("set-cookie", headers.name(2)); - assertEquals("Cookie1", headers.value(2)); + assertEquals(Protocol.SPDY_3.name.utf8(), headers.get(OkHeaders.SELECTED_PROTOCOL)); + assertEquals(OkHeaders.SELECTED_PROTOCOL, headers.name(1)); + assertEquals(Protocol.SPDY_3.name.utf8(), headers.value(1)); + assertEquals("cache-control", headers.name(2)); + assertEquals("no-cache, no-store", headers.value(2)); assertEquals("set-cookie", headers.name(3)); - assertEquals("Cookie2", headers.value(3)); + assertEquals("Cookie1", headers.value(3)); + assertEquals("set-cookie", headers.name(4)); + assertEquals("Cookie2", headers.value(4)); assertNull(headers.get(":status")); assertNull(headers.get(":version")); } @@ -62,11 +65,13 @@ public final class HeadersTest { "connection", "close"); Request request = new Request.Builder().url("http://square.com/").build(); Response response = - SpdyTransport.readNameValueBlock(nameValueBlock, "spdy/3").request(request).build(); + SpdyTransport.readNameValueBlock(nameValueBlock, Protocol.SPDY_3).request(request).build(); Headers headers = response.headers(); - assertEquals(1, headers.size()); + assertEquals(2, headers.size()); assertEquals(OkHeaders.SELECTED_TRANSPORT, headers.name(0)); - assertEquals("spdy/3", headers.value(0));; + assertEquals(Protocol.SPDY_3.name.utf8(), headers.value(0)); + assertEquals(OkHeaders.SELECTED_PROTOCOL, headers.name(1)); + assertEquals(Protocol.SPDY_3.name.utf8(), headers.value(1));; } @Test public void readNameValueBlockDropsForbiddenHeadersHttp2() throws IOException { @@ -75,12 +80,14 @@ public final class HeadersTest { ":version", "HTTP/1.1", "connection", "close"); Request request = new Request.Builder().url("http://square.com/").build(); - Response response = - SpdyTransport.readNameValueBlock(nameValueBlock, "HTTP-draft-09/2.0").request(request).build(); + Response response = SpdyTransport.readNameValueBlock(nameValueBlock, Protocol.HTTP_2) + .request(request).build(); Headers headers = response.headers(); - assertEquals(1, headers.size()); + assertEquals(2, headers.size()); assertEquals(OkHeaders.SELECTED_TRANSPORT, headers.name(0)); - assertEquals("HTTP-draft-09/2.0", headers.value(0));; + assertEquals(Protocol.HTTP_2.name.utf8(), headers.value(0)); + assertEquals(OkHeaders.SELECTED_PROTOCOL, headers.name(1)); + assertEquals(Protocol.HTTP_2.name.utf8(), headers.value(1)); } @Test public void toNameValueBlock() { @@ -91,7 +98,8 @@ public final class HeadersTest { .addHeader("set-cookie", "Cookie2") .header(":status", "200 OK") .build(); - List nameValueBlock = SpdyTransport.writeNameValueBlock(request, "spdy/3", "HTTP/1.1"); + List nameValueBlock = + SpdyTransport.writeNameValueBlock(request, Protocol.SPDY_3, "HTTP/1.1"); List expected = byteStringList( ":method", "GET", ":path", "/", @@ -116,7 +124,7 @@ public final class HeadersTest { ":version", "HTTP/1.1", ":host", "square.com", ":scheme", "http"); - assertEquals(expected, SpdyTransport.writeNameValueBlock(request, "spdy/3", "HTTP/1.1")); + assertEquals(expected, SpdyTransport.writeNameValueBlock(request, Protocol.SPDY_3, "HTTP/1.1")); } @Test public void toNameValueBlockDropsForbiddenHeadersHttp2() { @@ -132,6 +140,6 @@ public final class HeadersTest { ":authority", "square.com", ":scheme", "http"); assertEquals(expected, - SpdyTransport.writeNameValueBlock(request, "HTTP-draft-09/2.0", "HTTP/1.1")); + SpdyTransport.writeNameValueBlock(request, Protocol.HTTP_2, "HTTP/1.1")); } } diff --git a/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverHttp20Draft09Test.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverHttp20Draft09Test.java index dfc962be9..c1f4d3cf8 100644 --- a/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverHttp20Draft09Test.java +++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverHttp20Draft09Test.java @@ -15,12 +15,12 @@ */ package com.squareup.okhttp.internal.http; +import com.squareup.okhttp.Protocol; + public class HttpOverHttp20Draft09Test extends HttpOverSpdyTest { public HttpOverHttp20Draft09Test() { - super("HTTP-draft-09/2.0"); - // TODO: is this really the whole authority, or just the host/port? - // https://github.com/http2/http2-spec/issues/334 + super(Protocol.HTTP_2); this.hostHeader = ":authority"; } } diff --git a/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverSpdy3Test.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverSpdy3Test.java index 4c30e45f4..4020bf4c2 100644 --- a/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverSpdy3Test.java +++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverSpdy3Test.java @@ -15,9 +15,11 @@ */ package com.squareup.okhttp.internal.http; +import com.squareup.okhttp.Protocol; + public class HttpOverSpdy3Test extends HttpOverSpdyTest { public HttpOverSpdy3Test() { - super("spdy/3"); + super(Protocol.SPDY_3); } } diff --git a/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverSpdyTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverSpdyTest.java index e2994a9e3..223fb8e1a 100644 --- a/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverSpdyTest.java +++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverSpdyTest.java @@ -17,6 +17,7 @@ package com.squareup.okhttp.internal.http; import com.squareup.okhttp.HttpResponseCache; import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Protocol; import com.squareup.okhttp.internal.RecordingAuthenticator; import com.squareup.okhttp.internal.SslContextBuilder; import com.squareup.okhttp.internal.Util; @@ -61,12 +62,12 @@ import static org.junit.Assert.fail; /** Test how SPDY interacts with HTTP features. */ public abstract class HttpOverSpdyTest { - /** Transport to test, for example {@code spdy/3} */ - private final String transport; + /** Protocol to test, for example {@link com.squareup.okhttp.Protocol#SPDY_3} */ + private final Protocol protocol; protected String hostHeader = ":host"; - protected HttpOverSpdyTest(String transport){ - this.transport = transport; + protected HttpOverSpdyTest(Protocol protocol){ + this.protocol = protocol; } private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = new HostnameVerifier() { @@ -83,11 +84,11 @@ public abstract class HttpOverSpdyTest { @Before public void setUp() throws Exception { server.useHttps(sslContext.getSocketFactory(), false); - client.setTransports(Arrays.asList(transport, "http/1.1")); + client.setProtocols(Arrays.asList(protocol, Protocol.HTTP_11)); client.setSslSocketFactory(sslContext.getSocketFactory()); client.setHostnameVerifier(NULL_HOSTNAME_VERIFIER); String systemTmpDir = System.getProperty("java.io.tmpdir"); - File cacheDir = new File(systemTmpDir, "HttpCache-" + transport + "-" + UUID.randomUUID()); + File cacheDir = new File(systemTmpDir, "HttpCache-" + protocol + "-" + UUID.randomUUID()); cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE); } @@ -386,8 +387,7 @@ public abstract class HttpOverSpdyTest { @Override public void run() { try { - HttpURLConnection conn = null; - conn = (HttpURLConnection) client.open(server.getUrl(path)); + HttpURLConnection conn = client.open(server.getUrl(path)); assertEquals("A", readAscii(conn.getInputStream(), 1)); countDownLatch.countDown(); } catch (Exception e) { diff --git a/okhttp/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java index bbb36d42a..4275b7555 100644 --- a/okhttp/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java +++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java @@ -21,6 +21,7 @@ import com.squareup.okhttp.ConnectionPool; import com.squareup.okhttp.OkAuthenticator; import com.squareup.okhttp.RouteDatabase; import com.squareup.okhttp.internal.Dns; +import com.squareup.okhttp.Protocol; import com.squareup.okhttp.internal.SslContextBuilder; import java.io.IOException; import java.net.InetAddress; @@ -75,12 +76,12 @@ public final class RouteSelectorTest { } private final OkAuthenticator authenticator = HttpAuthenticator.SYSTEM_DEFAULT; - private final List transports = Arrays.asList("http/1.1"); + private final List protocols = Arrays.asList(Protocol.HTTP_11); private final FakeDns dns = new FakeDns(); private final FakeProxySelector proxySelector = new FakeProxySelector(); @Test public void singleRoute() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null, protocols); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, new RouteDatabase()); @@ -99,7 +100,7 @@ public final class RouteSelectorTest { } @Test public void singleRouteReturnsFailedRoute() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null, protocols); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, new RouteDatabase()); @@ -120,7 +121,7 @@ public final class RouteSelectorTest { } @Test public void explicitProxyTriesThatProxiesAddressesOnly() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, authenticator, proxyA, transports); + Address address = new Address(uriHost, uriPort, null, null, authenticator, proxyA, protocols); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, new RouteDatabase()); @@ -138,7 +139,7 @@ public final class RouteSelectorTest { @Test public void explicitDirectProxy() throws Exception { Address address = new Address(uriHost, uriPort, null, null, authenticator, NO_PROXY, - transports); + protocols); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, new RouteDatabase()); @@ -155,7 +156,7 @@ public final class RouteSelectorTest { } @Test public void proxySelectorReturnsNull() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null, protocols); proxySelector.proxies = null; RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, @@ -172,7 +173,7 @@ public final class RouteSelectorTest { } @Test public void proxySelectorReturnsNoProxies() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null, protocols); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, new RouteDatabase()); @@ -189,7 +190,7 @@ public final class RouteSelectorTest { } @Test public void proxySelectorReturnsMultipleProxies() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null, protocols); proxySelector.proxies.add(proxyA); proxySelector.proxies.add(proxyB); @@ -224,7 +225,7 @@ public final class RouteSelectorTest { } @Test public void proxySelectorDirectConnectionsAreSkipped() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null, protocols); proxySelector.proxies.add(NO_PROXY); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, @@ -242,7 +243,7 @@ public final class RouteSelectorTest { } @Test public void proxyDnsFailureContinuesToNextProxy() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null, protocols); proxySelector.proxies.add(proxyA); proxySelector.proxies.add(proxyB); @@ -284,7 +285,7 @@ public final class RouteSelectorTest { // https://github.com/square/okhttp/issues/442 @Test public void nonSslErrorAddsAllTlsModesToFailedRoute() throws Exception { Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator, - Proxy.NO_PROXY, transports); + Proxy.NO_PROXY, protocols); RouteDatabase routeDatabase = new RouteDatabase(); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, routeDatabase); @@ -298,7 +299,7 @@ public final class RouteSelectorTest { @Test public void sslErrorAddsOnlyFailedTlsModeToFailedRoute() throws Exception { Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator, - Proxy.NO_PROXY, transports); + Proxy.NO_PROXY, protocols); RouteDatabase routeDatabase = new RouteDatabase(); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, routeDatabase); @@ -312,7 +313,7 @@ public final class RouteSelectorTest { @Test public void multipleProxiesMultipleInetAddressesMultipleTlsModes() throws Exception { Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator, - null, transports); + null, protocols); proxySelector.proxies.add(proxyA); proxySelector.proxies.add(proxyB); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, @@ -359,7 +360,7 @@ public final class RouteSelectorTest { @Test public void failedRoutesAreLast() throws Exception { Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator, - Proxy.NO_PROXY, transports); + Proxy.NO_PROXY, protocols); RouteDatabase routeDatabase = new RouteDatabase(); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, diff --git a/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java index b1abccd97..04734ccbc 100644 --- a/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java +++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java @@ -18,6 +18,7 @@ package com.squareup.okhttp.internal.http; import com.squareup.okhttp.HttpResponseCache; import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Protocol; import com.squareup.okhttp.internal.RecordingAuthenticator; import com.squareup.okhttp.internal.RecordingHostnameVerifier; import com.squareup.okhttp.internal.RecordingOkAuthenticator; @@ -2500,24 +2501,24 @@ public final class URLConnectionTest { assertTrue(call, call.contains("challenges=[Basic realm=\"protected area\"]")); } - @Test public void setTransports() throws Exception { + @Test public void setProtocols() throws Exception { server.enqueue(new MockResponse().setBody("A")); server.play(); - client.setTransports(Arrays.asList("http/1.1")); + client.setProtocols(Arrays.asList(Protocol.HTTP_11)); assertContent("A", client.open(server.getUrl("/"))); } - @Test public void setTransportsWithoutHttp11() throws Exception { + @Test public void setProtocolsWithoutHttp11() throws Exception { try { - client.setTransports(Arrays.asList("spdy/3")); + client.setProtocols(Arrays.asList(Protocol.SPDY_3)); fail(); } catch (IllegalArgumentException expected) { } } - @Test public void setTransportsWithNull() throws Exception { + @Test public void setProtocolsWithNull() throws Exception { try { - client.setTransports(Arrays.asList("http/1.1", null)); + client.setProtocols(Arrays.asList(Protocol.HTTP_11, null)); fail(); } catch (IllegalArgumentException expected) { }