Home | History | Annotate | Download | only in okhttp
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 package com.squareup.okhttp;
     18 
     19 import com.squareup.okhttp.internal.Platform;
     20 import com.squareup.okhttp.internal.Util;
     21 import com.squareup.okhttp.internal.http.HttpAuthenticator;
     22 import com.squareup.okhttp.internal.http.HttpConnection;
     23 import com.squareup.okhttp.internal.http.HttpEngine;
     24 import com.squareup.okhttp.internal.http.HttpTransport;
     25 import com.squareup.okhttp.internal.http.OkHeaders;
     26 import com.squareup.okhttp.internal.http.SpdyTransport;
     27 import com.squareup.okhttp.internal.spdy.SpdyConnection;
     28 import java.io.Closeable;
     29 import java.io.IOException;
     30 import java.net.Proxy;
     31 import java.net.Socket;
     32 import javax.net.ssl.SSLSocket;
     33 import okio.ByteString;
     34 import okio.OkBuffer;
     35 import okio.Source;
     36 
     37 import static java.net.HttpURLConnection.HTTP_OK;
     38 import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
     39 
     40 /**
     41  * The sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection. May be
     42  * used for multiple HTTP request/response exchanges. Connections may be direct
     43  * to the origin server or via a proxy.
     44  *
     45  * <p>Typically instances of this class are created, connected and exercised
     46  * automatically by the HTTP client. Applications may use this class to monitor
     47  * HTTP connections as members of a {@link ConnectionPool connection pool}.
     48  *
     49  * <p>Do not confuse this class with the misnamed {@code HttpURLConnection},
     50  * which isn't so much a connection as a single request/response exchange.
     51  *
     52  * <h3>Modern TLS</h3>
     53  * There are tradeoffs when selecting which options to include when negotiating
     54  * a secure connection to a remote host. Newer TLS options are quite useful:
     55  * <ul>
     56  *   <li>Server Name Indication (SNI) enables one IP address to negotiate secure
     57  *       connections for multiple domain names.
     58  *   <li>Next Protocol Negotiation (NPN) enables the HTTPS port (443) to be used
     59  *       for both HTTP and SPDY protocols.
     60  * </ul>
     61  * Unfortunately, older HTTPS servers refuse to connect when such options are
     62  * presented. Rather than avoiding these options entirely, this class allows a
     63  * connection to be attempted with modern options and then retried without them
     64  * should the attempt fail.
     65  */
     66 public final class Connection implements Closeable {
     67   private final ConnectionPool pool;
     68   private final Route route;
     69 
     70   private Socket socket;
     71   private boolean connected = false;
     72   private HttpConnection httpConnection;
     73   private SpdyConnection spdyConnection;
     74   private int httpMinorVersion = 1; // Assume HTTP/1.1
     75   private long idleStartTimeNs;
     76   private Handshake handshake;
     77   private int recycleCount;
     78 
     79   /**
     80    * The object that owns this connection. Null if it is shared (for SPDY),
     81    * belongs to a pool, or has been discarded. Guarded by {@code pool}, which
     82    * clears the owner when an incoming connection is recycled.
     83    */
     84   private Object owner;
     85 
     86   public Connection(ConnectionPool pool, Route route) {
     87     this.pool = pool;
     88     this.route = route;
     89   }
     90 
     91   public Object getOwner() {
     92     synchronized (pool) {
     93       return owner;
     94     }
     95   }
     96 
     97   public void setOwner(Object owner) {
     98     if (isSpdy()) return; // SPDY connections are shared.
     99     synchronized (pool) {
    100       if (this.owner != null) throw new IllegalStateException("Connection already has an owner!");
    101       this.owner = owner;
    102     }
    103   }
    104 
    105   /**
    106    * Attempts to clears the owner of this connection. Returns true if the owner
    107    * was cleared and the connection can be pooled or reused. This will return
    108    * false if the connection cannot be pooled or reused, such as if it was
    109    * closed with {@link #closeIfOwnedBy}.
    110    */
    111   public boolean clearOwner() {
    112     synchronized (pool) {
    113       if (owner == null) {
    114         // No owner? Don't reuse this connection.
    115         return false;
    116       }
    117 
    118       owner = null;
    119       return true;
    120     }
    121   }
    122 
    123   /**
    124    * Closes this connection if it is currently owned by {@code owner}. This also
    125    * strips the ownership of the connection so it cannot be pooled or reused.
    126    */
    127   public void closeIfOwnedBy(Object owner) throws IOException {
    128     if (isSpdy()) throw new IllegalStateException();
    129     synchronized (pool) {
    130       if (this.owner != owner) {
    131         return; // Wrong owner. Perhaps a late disconnect?
    132       }
    133 
    134       this.owner = null; // Drop the owner so the connection won't be reused.
    135     }
    136 
    137     // Don't close() inside the synchronized block.
    138     socket.close();
    139   }
    140 
    141   public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest)
    142       throws IOException {
    143     if (connected) throw new IllegalStateException("already connected");
    144 
    145     if (route.proxy.type() == Proxy.Type.DIRECT || route.proxy.type() == Proxy.Type.HTTP) {
    146       socket = route.address.socketFactory.createSocket();
    147     } else {
    148       socket = new Socket(route.proxy);
    149     }
    150 
    151     socket.setSoTimeout(readTimeout);
    152     Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout);
    153 
    154     if (route.address.sslSocketFactory != null) {
    155       upgradeToTls(tunnelRequest);
    156     } else {
    157       httpConnection = new HttpConnection(pool, this, socket);
    158     }
    159     connected = true;
    160   }
    161 
    162   /**
    163    * Create an {@code SSLSocket} and perform the TLS handshake and certificate
    164    * validation.
    165    */
    166   private void upgradeToTls(TunnelRequest tunnelRequest) throws IOException {
    167     Platform platform = Platform.get();
    168 
    169     // Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
    170     if (requiresTunnel()) {
    171       makeTunnel(tunnelRequest);
    172     }
    173 
    174     // Create the wrapper over connected socket.
    175     socket = route.address.sslSocketFactory
    176         .createSocket(socket, route.address.uriHost, route.address.uriPort, true /* autoClose */);
    177     SSLSocket sslSocket = (SSLSocket) socket;
    178     if (route.modernTls) {
    179       platform.enableTlsExtensions(sslSocket, route.address.uriHost);
    180     } else {
    181       platform.supportTlsIntolerantServer(sslSocket);
    182     }
    183 
    184     boolean useNpn = false;
    185     if (route.modernTls) {
    186       boolean http2 = route.address.protocols.contains(Protocol.HTTP_2);
    187       boolean spdy3 = route.address.protocols.contains(Protocol.SPDY_3);
    188       if (http2 && spdy3) {
    189         platform.setNpnProtocols(sslSocket, Protocol.HTTP2_SPDY3_AND_HTTP);
    190         useNpn = true;
    191       } else if (http2) {
    192         platform.setNpnProtocols(sslSocket, Protocol.HTTP2_AND_HTTP_11);
    193         useNpn = true;
    194       } else if (spdy3) {
    195         platform.setNpnProtocols(sslSocket, Protocol.SPDY3_AND_HTTP11);
    196         useNpn = true;
    197       }
    198     }
    199 
    200     // Force handshake. This can throw!
    201     sslSocket.startHandshake();
    202 
    203     // Verify that the socket's certificates are acceptable for the target host.
    204     if (!route.address.hostnameVerifier.verify(route.address.uriHost, sslSocket.getSession())) {
    205       throw new IOException("Hostname '" + route.address.uriHost + "' was not verified");
    206     }
    207 
    208     handshake = Handshake.get(sslSocket.getSession());
    209 
    210     ByteString maybeProtocol;
    211     Protocol selectedProtocol = Protocol.HTTP_11;
    212     if (useNpn && (maybeProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
    213       selectedProtocol = Protocol.find(maybeProtocol); // Throws IOE on unknown.
    214     }
    215 
    216     if (selectedProtocol.spdyVariant) {
    217       sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
    218       spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, socket)
    219           .protocol(selectedProtocol).build();
    220       spdyConnection.sendConnectionHeader();
    221     } else {
    222       httpConnection = new HttpConnection(pool, this, socket);
    223     }
    224   }
    225 
    226   /** Returns true if {@link #connect} has been attempted on this connection. */
    227   public boolean isConnected() {
    228     return connected;
    229   }
    230 
    231   @Override public void close() throws IOException {
    232     if (socket != null) socket.close();
    233   }
    234 
    235   /** Returns the route used by this connection. */
    236   public Route getRoute() {
    237     return route;
    238   }
    239 
    240   /**
    241    * Returns the socket that this connection uses, or null if the connection
    242    * is not currently connected.
    243    */
    244   public Socket getSocket() {
    245     return socket;
    246   }
    247 
    248   /** Returns true if this connection is alive. */
    249   public boolean isAlive() {
    250     return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown();
    251   }
    252 
    253   /**
    254    * Returns true if we are confident that we can read data from this
    255    * connection. This is more expensive and more accurate than {@link
    256    * #isAlive()}; callers should check {@link #isAlive()} first.
    257    */
    258   public boolean isReadable() {
    259     if (httpConnection != null) return httpConnection.isReadable();
    260     return true; // SPDY connections, and connections before connect() are both optimistic.
    261   }
    262 
    263   public void resetIdleStartTime() {
    264     if (spdyConnection != null) throw new IllegalStateException("spdyConnection != null");
    265     this.idleStartTimeNs = System.nanoTime();
    266   }
    267 
    268   /** Returns true if this connection is idle. */
    269   public boolean isIdle() {
    270     return spdyConnection == null || spdyConnection.isIdle();
    271   }
    272 
    273   /**
    274    * Returns true if this connection has been idle for longer than
    275    * {@code keepAliveDurationNs}.
    276    */
    277   public boolean isExpired(long keepAliveDurationNs) {
    278     return getIdleStartTimeNs() < System.nanoTime() - keepAliveDurationNs;
    279   }
    280 
    281   /**
    282    * Returns the time in ns when this connection became idle. Undefined if
    283    * this connection is not idle.
    284    */
    285   public long getIdleStartTimeNs() {
    286     return spdyConnection == null ? idleStartTimeNs : spdyConnection.getIdleStartTimeNs();
    287   }
    288 
    289   public Handshake getHandshake() {
    290     return handshake;
    291   }
    292 
    293   /** Returns the transport appropriate for this connection. */
    294   public Object newTransport(HttpEngine httpEngine) throws IOException {
    295     return (spdyConnection != null)
    296         ? new SpdyTransport(httpEngine, spdyConnection)
    297         : new HttpTransport(httpEngine, httpConnection);
    298   }
    299 
    300   /**
    301    * Returns true if this is a SPDY connection. Such connections can be used
    302    * in multiple HTTP requests simultaneously.
    303    */
    304   public boolean isSpdy() {
    305     return spdyConnection != null;
    306   }
    307 
    308   /**
    309    * Returns the minor HTTP version that should be used for future requests on
    310    * this connection. Either 0 for HTTP/1.0, or 1 for HTTP/1.1. The default
    311    * value is 1 for new connections.
    312    */
    313   public int getHttpMinorVersion() {
    314     return httpMinorVersion;
    315   }
    316 
    317   public void setHttpMinorVersion(int httpMinorVersion) {
    318     this.httpMinorVersion = httpMinorVersion;
    319   }
    320 
    321   /**
    322    * Returns true if the HTTP connection needs to tunnel one protocol over
    323    * another, such as when using HTTPS through an HTTP proxy. When doing so,
    324    * we must avoid buffering bytes intended for the higher-level protocol.
    325    */
    326   public boolean requiresTunnel() {
    327     return route.address.sslSocketFactory != null && route.proxy.type() == Proxy.Type.HTTP;
    328   }
    329 
    330   public void updateReadTimeout(int newTimeout) throws IOException {
    331     if (!connected) throw new IllegalStateException("updateReadTimeout - not connected");
    332     socket.setSoTimeout(newTimeout);
    333   }
    334 
    335   public void incrementRecycleCount() {
    336     recycleCount++;
    337   }
    338 
    339   /**
    340    * Returns the number of times this connection has been returned to the
    341    * connection pool.
    342    */
    343   public int recycleCount() {
    344     return recycleCount;
    345   }
    346 
    347   /**
    348    * To make an HTTPS connection over an HTTP proxy, send an unencrypted
    349    * CONNECT request to create the proxy connection. This may need to be
    350    * retried if the proxy requires authorization.
    351    */
    352   private void makeTunnel(TunnelRequest tunnelRequest) throws IOException {
    353     HttpConnection tunnelConnection = new HttpConnection(pool, this, socket);
    354     Request request = tunnelRequest.getRequest();
    355     String requestLine = tunnelRequest.requestLine();
    356     while (true) {
    357       tunnelConnection.writeRequest(request.headers(), requestLine);
    358       tunnelConnection.flush();
    359       Response response = tunnelConnection.readResponse().request(request).build();
    360       // The response body from a CONNECT should be empty, but if it is not then we should consume
    361       // it before proceeding.
    362       long contentLength = OkHeaders.contentLength(response);
    363       if (contentLength != -1) {
    364         Source body = tunnelConnection.newFixedLengthSource(null, contentLength);
    365         Util.skipAll(body, Integer.MAX_VALUE);
    366       } else {
    367         tunnelConnection.emptyResponseBody();
    368       }
    369 
    370       switch (response.code()) {
    371         case HTTP_OK:
    372           // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If that
    373           // happens, then we will have buffered bytes that are needed by the SSLSocket!
    374           // This check is imperfect: it doesn't tell us whether a handshake will succeed, just that
    375           // it will almost certainly fail because the proxy has sent unexpected data.
    376           if (tunnelConnection.bufferSize() > 0) {
    377             throw new IOException("TLS tunnel buffered too many bytes!");
    378           }
    379           return;
    380 
    381         case HTTP_PROXY_AUTH:
    382           request = HttpAuthenticator.processAuthHeader(
    383               route.address.authenticator, response, route.proxy);
    384           if (request != null) continue;
    385           throw new IOException("Failed to authenticate with proxy");
    386 
    387         default:
    388           throw new IOException(
    389               "Unexpected response code for CONNECT: " + response.code());
    390       }
    391     }
    392   }
    393 }
    394