From 8d78da7ce7e60d93fe8e299a83b963cd220a60ad Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Wed, 12 Mar 2014 00:08:41 -0400 Subject: [PATCH] Push Socket into HttpConnection/SpdyConnection. --- .../java/com/squareup/okhttp/Connection.java | 77 ++++--------------- .../okhttp/internal/http/HttpConnection.java | 37 +++++++-- .../okhttp/internal/spdy/SpdyConnection.java | 10 +-- 3 files changed, 54 insertions(+), 70 deletions(-) diff --git a/okhttp/src/main/java/com/squareup/okhttp/Connection.java b/okhttp/src/main/java/com/squareup/okhttp/Connection.java index d9115a966..55884dcf4 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Connection.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Connection.java @@ -25,16 +25,10 @@ import com.squareup.okhttp.internal.http.SpdyTransport; import com.squareup.okhttp.internal.spdy.SpdyConnection; import java.io.Closeable; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.Proxy; import java.net.Socket; -import java.net.SocketTimeoutException; import javax.net.ssl.SSLSocket; -import okio.BufferedSink; -import okio.BufferedSource; import okio.ByteString; -import okio.Okio; import static java.net.HttpURLConnection.HTTP_OK; import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; @@ -70,10 +64,6 @@ public final class Connection implements Closeable { private final Route route; private Socket socket; - private InputStream in; - private OutputStream out; - private BufferedSource source; - private BufferedSink sink; private boolean connected = false; private HttpConnection httpConnection; private SpdyConnection spdyConnection; @@ -94,14 +84,11 @@ public final class Connection implements Closeable { socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket(); Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout); socket.setSoTimeout(readTimeout); - in = socket.getInputStream(); - out = socket.getOutputStream(); if (route.address.sslSocketFactory != null) { upgradeToTls(tunnelRequest); } else { - initSourceAndSink(); - httpConnection = new HttpConnection(pool, this, source, sink); + httpConnection = new HttpConnection(pool, this, socket); } connected = true; } @@ -128,19 +115,19 @@ public final class Connection implements Closeable { platform.supportTlsIntolerantServer(sslSocket); } - 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.protocols.contains(Protocol.HTTP_2) // Contains both spdy variants. - && route.address.protocols.contains(Protocol.SPDY_3)) { + boolean useNpn = false; + if (route.modernTls) { + boolean http2 = route.address.protocols.contains(Protocol.HTTP_2); + boolean spdy3 = route.address.protocols.contains(Protocol.SPDY_3); + if (http2 && spdy3) { platform.setNpnProtocols(sslSocket, Protocol.HTTP2_SPDY3_AND_HTTP); - } else if (route.address.protocols.contains(Protocol.HTTP_2)) { + useNpn = true; + } else if (http2) { platform.setNpnProtocols(sslSocket, Protocol.HTTP2_AND_HTTP_11); - } else { + useNpn = true; + } else if (spdy3) { platform.setNpnProtocols(sslSocket, Protocol.SPDY3_AND_HTTP11); + useNpn = true; } } @@ -152,10 +139,7 @@ public final class Connection implements Closeable { throw new IOException("Hostname '" + route.address.uriHost + "' was not verified"); } - out = sslSocket.getOutputStream(); - in = sslSocket.getInputStream(); handshake = Handshake.get(sslSocket.getSession()); - initSourceAndSink(); ByteString maybeProtocol; Protocol selectedProtocol = Protocol.HTTP_11; @@ -165,11 +149,11 @@ public final class Connection implements Closeable { if (selectedProtocol.spdyVariant) { sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream. - spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, source, sink) + spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, socket) .protocol(selectedProtocol).build(); spdyConnection.sendConnectionHeader(); } else { - httpConnection = new HttpConnection(pool, this, source, sink); + httpConnection = new HttpConnection(pool, this, socket); } } @@ -206,28 +190,8 @@ public final class Connection implements Closeable { * #isAlive()}; callers should check {@link #isAlive()} first. */ public boolean isReadable() { - if (source == null) { - return true; // Optimistic. - } - if (isSpdy()) { - return true; // Optimistic. We can't test SPDY because its streams are in use. - } - try { - int readTimeout = socket.getSoTimeout(); - try { - socket.setSoTimeout(1); - if (source.exhausted()) { - return false; // Stream is exhausted; socket is closed. - } - return true; - } finally { - socket.setSoTimeout(readTimeout); - } - } catch (SocketTimeoutException ignored) { - return true; // Read timed out; socket is good. - } catch (IOException e) { - return false; // Couldn't read; socket is closed. - } + if (httpConnection != null) return httpConnection.isReadable(); + return true; // SPDY connections, and connections before connect() are both optimistic. } public void resetIdleStartTime() { @@ -320,9 +284,7 @@ public final class Connection implements Closeable { * retried if the proxy requires authorization. */ private void makeTunnel(TunnelRequest tunnelRequest) throws IOException { - BufferedSource tunnelSource = Okio.buffer(Okio.source(in)); - BufferedSink tunnelSink = Okio.buffer(Okio.sink(out)); - HttpConnection tunnelConnection = new HttpConnection(pool, this, tunnelSource, tunnelSink); + HttpConnection tunnelConnection = new HttpConnection(pool, this, socket); Request request = tunnelRequest.getRequest(); String requestLine = tunnelRequest.requestLine(); while (true) { @@ -335,7 +297,7 @@ public final class Connection implements Closeable { case HTTP_OK: // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If that // happens, then we will have buffered bytes that are needed by the SSLSocket! - if (tunnelSource.buffer().size() > 0) { + if (tunnelConnection.bufferSize() > 0) { throw new IOException("TLS tunnel buffered too many bytes!"); } return; @@ -352,9 +314,4 @@ public final class Connection implements Closeable { } } } - - private void initSourceAndSink() throws IOException { - source = Okio.buffer(Okio.source(in)); - sink = Okio.buffer(Okio.sink(out)); - } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java index 34d1a2b20..b12b12d3d 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java @@ -27,6 +27,7 @@ import java.io.OutputStream; import java.net.CacheRequest; import java.net.ProtocolException; import java.net.Socket; +import java.net.SocketTimeoutException; import okio.BufferedSink; import okio.BufferedSource; import okio.Deadline; @@ -72,18 +73,20 @@ public final class HttpConnection { private final ConnectionPool pool; private final Connection connection; + private final Socket socket; private final BufferedSource source; private final BufferedSink sink; private int state = STATE_IDLE; private int onIdle = ON_IDLE_HOLD; - public HttpConnection(ConnectionPool pool, Connection connection, BufferedSource source, - BufferedSink sink) { + public HttpConnection(ConnectionPool pool, Connection connection, Socket socket) + throws IOException { this.pool = pool; this.connection = connection; - this.source = source; - this.sink = sink; + this.socket = socket; + this.source = Okio.buffer(Okio.source(socket.getInputStream())); + this.sink = Okio.buffer(Okio.sink(socket.getOutputStream())); } /** @@ -123,6 +126,31 @@ public final class HttpConnection { sink.flush(); } + /** Returns the number of buffered bytes immediately readable. */ + public long bufferSize() { + return source.buffer().size(); + } + + /** Test for a stale socket. */ + public boolean isReadable() { + try { + int readTimeout = socket.getSoTimeout(); + try { + socket.setSoTimeout(1); + if (source.exhausted()) { + return false; // Stream is exhausted; socket is closed. + } + return true; + } finally { + socket.setSoTimeout(readTimeout); + } + } catch (SocketTimeoutException ignored) { + return true; // Read timed out; socket is good. + } catch (IOException e) { + return false; // Couldn't read; socket is closed. + } + } + /** Returns bytes of a request header for sending on an HTTP transport. */ public void writeRequest(Headers headers, String requestLine) throws IOException { if (state != STATE_IDLE) throw new IllegalStateException("state: " + state); @@ -177,7 +205,6 @@ public final class HttpConnection { * that may never occur. */ public boolean discard(Source in, int timeoutMillis) { - Socket socket = connection.getSocket(); try { int socketTimeout = socket.getSoTimeout(); socket.setSoTimeout(timeoutMillis); diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java b/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java index 4cd8d32c8..da7c4e174 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java @@ -21,6 +21,7 @@ import com.squareup.okhttp.internal.Util; import java.io.Closeable; import java.io.IOException; import java.io.InterruptedIOException; +import java.net.InetSocketAddress; import java.net.Socket; import java.util.HashMap; import java.util.Iterator; @@ -499,19 +500,18 @@ public final class SpdyConnection implements Closeable { private boolean client; public Builder(boolean client, Socket socket) throws IOException { - this("", client, Okio.buffer(Okio.source(socket.getInputStream())), - Okio.buffer(Okio.sink(socket.getOutputStream()))); + this(((InetSocketAddress) socket.getRemoteSocketAddress()).getHostName(), client, socket); } /** * @param client true if this peer initiated the connection; false if this * peer accepted the connection. */ - public Builder(String hostName, boolean client, BufferedSource source, BufferedSink sink) { + public Builder(String hostName, boolean client, Socket socket) throws IOException { this.hostName = hostName; this.client = client; - this.source = source; - this.sink = sink; + this.source = Okio.buffer(Okio.source(socket.getInputStream())); + this.sink = Okio.buffer(Okio.sink(socket.getOutputStream())); } public Builder handler(IncomingStreamHandler handler) {