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.http.HttpConnection;
     20 import com.squareup.okhttp.internal.http.HttpEngine;
     21 import com.squareup.okhttp.internal.http.HttpTransport;
     22 import com.squareup.okhttp.internal.http.RouteException;
     23 import com.squareup.okhttp.internal.http.SocketConnector;
     24 import com.squareup.okhttp.internal.http.SpdyTransport;
     25 import com.squareup.okhttp.internal.http.Transport;
     26 import com.squareup.okhttp.internal.spdy.SpdyConnection;
     27 import java.io.IOException;
     28 import java.net.Socket;
     29 import java.net.UnknownServiceException;
     30 import java.util.List;
     31 import okio.BufferedSink;
     32 import okio.BufferedSource;
     33 
     34 /**
     35  * The sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection. May be
     36  * used for multiple HTTP request/response exchanges. Connections may be direct
     37  * to the origin server or via a proxy.
     38  *
     39  * <p>Typically instances of this class are created, connected and exercised
     40  * automatically by the HTTP client. Applications may use this class to monitor
     41  * HTTP connections as members of a {@linkplain ConnectionPool connection pool}.
     42  *
     43  * <p>Do not confuse this class with the misnamed {@code HttpURLConnection},
     44  * which isn't so much a connection as a single request/response exchange.
     45  *
     46  * <h3>Modern TLS</h3>
     47  * There are tradeoffs when selecting which options to include when negotiating
     48  * a secure connection to a remote host. Newer TLS options are quite useful:
     49  * <ul>
     50  *   <li>Server Name Indication (SNI) enables one IP address to negotiate secure
     51  *       connections for multiple domain names.
     52  *   <li>Application Layer Protocol Negotiation (ALPN) enables the HTTPS port
     53  *       (443) to be used for different HTTP and SPDY protocols.
     54  * </ul>
     55  * Unfortunately, older HTTPS servers refuse to connect when such options are
     56  * presented. Rather than avoiding these options entirely, this class allows a
     57  * connection to be attempted with modern options and then retried without them
     58  * should the attempt fail.
     59  */
     60 public final class Connection {
     61   private final ConnectionPool pool;
     62   private final Route route;
     63 
     64   private Socket socket;
     65   private boolean connected = false;
     66   private HttpConnection httpConnection;
     67   private SpdyConnection spdyConnection;
     68   private Protocol protocol = Protocol.HTTP_1_1;
     69   private long idleStartTimeNs;
     70   private Handshake handshake;
     71   private int recycleCount;
     72 
     73   /**
     74    * The object that owns this connection. Null if it is shared (for SPDY),
     75    * belongs to a pool, or has been discarded. Guarded by {@code pool}, which
     76    * clears the owner when an incoming connection is recycled.
     77    */
     78   private Object owner;
     79 
     80   public Connection(ConnectionPool pool, Route route) {
     81     this.pool = pool;
     82     this.route = route;
     83   }
     84 
     85   Object getOwner() {
     86     synchronized (pool) {
     87       return owner;
     88     }
     89   }
     90 
     91   void setOwner(Object owner) {
     92     if (isSpdy()) return; // SPDY connections are shared.
     93     synchronized (pool) {
     94       if (this.owner != null) throw new IllegalStateException("Connection already has an owner!");
     95       this.owner = owner;
     96     }
     97   }
     98 
     99   /**
    100    * Attempts to clears the owner of this connection. Returns true if the owner
    101    * was cleared and the connection can be pooled or reused. This will return
    102    * false if the connection cannot be pooled or reused, such as if it was
    103    * closed with {@link #closeIfOwnedBy}.
    104    */
    105   boolean clearOwner() {
    106     synchronized (pool) {
    107       if (owner == null) {
    108         // No owner? Don't reuse this connection.
    109         return false;
    110       }
    111 
    112       owner = null;
    113       return true;
    114     }
    115   }
    116 
    117   /**
    118    * Closes this connection if it is currently owned by {@code owner}. This also
    119    * strips the ownership of the connection so it cannot be pooled or reused.
    120    */
    121   void closeIfOwnedBy(Object owner) throws IOException {
    122     if (isSpdy()) throw new IllegalStateException();
    123     synchronized (pool) {
    124       if (this.owner != owner) {
    125         return; // Wrong owner. Perhaps a late disconnect?
    126       }
    127 
    128       this.owner = null; // Drop the owner so the connection won't be reused.
    129     }
    130 
    131     // Don't close() inside the synchronized block.
    132     socket.close();
    133   }
    134 
    135   void connect(int connectTimeout, int readTimeout, int writeTimeout, Request request,
    136       List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
    137     if (connected) throw new IllegalStateException("already connected");
    138 
    139     SocketConnector socketConnector = new SocketConnector(this, pool);
    140     SocketConnector.ConnectedSocket connectedSocket;
    141     if (route.address.getSslSocketFactory() != null) {
    142       // https:// communication
    143       connectedSocket = socketConnector.connectTls(connectTimeout, readTimeout, writeTimeout,
    144           request, route, connectionSpecs, connectionRetryEnabled);
    145     } else {
    146       // http:// communication.
    147       if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
    148         throw new RouteException(
    149             new UnknownServiceException(
    150                 "CLEARTEXT communication not supported: " + connectionSpecs));
    151       }
    152       connectedSocket = socketConnector.connectCleartext(connectTimeout, readTimeout, route);
    153     }
    154 
    155     socket = connectedSocket.socket;
    156     handshake = connectedSocket.handshake;
    157     protocol = connectedSocket.alpnProtocol == null
    158         ? Protocol.HTTP_1_1 : connectedSocket.alpnProtocol;
    159 
    160     try {
    161       if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
    162         socket.setSoTimeout(0); // SPDY timeouts are set per-stream.
    163         spdyConnection = new SpdyConnection.Builder(route.address.uriHost, true, socket)
    164             .protocol(protocol).build();
    165         spdyConnection.sendConnectionPreface();
    166       } else {
    167         httpConnection = new HttpConnection(pool, this, socket);
    168       }
    169     } catch (IOException e) {
    170       throw new RouteException(e);
    171     }
    172     connected = true;
    173   }
    174 
    175   /**
    176    * Connects this connection if it isn't already. This creates tunnels, shares
    177    * the connection with the connection pool, and configures timeouts.
    178    */
    179   void connectAndSetOwner(OkHttpClient client, Object owner, Request request)
    180       throws RouteException {
    181     setOwner(owner);
    182 
    183     if (!isConnected()) {
    184       List<ConnectionSpec> connectionSpecs = route.address.getConnectionSpecs();
    185       connect(client.getConnectTimeout(), client.getReadTimeout(), client.getWriteTimeout(),
    186           request, connectionSpecs, client.getRetryOnConnectionFailure());
    187       if (isSpdy()) {
    188         client.getConnectionPool().share(this);
    189       }
    190       client.routeDatabase().connected(getRoute());
    191     }
    192 
    193     setTimeouts(client.getReadTimeout(), client.getWriteTimeout());
    194   }
    195 
    196   /** Returns true if {@link #connect} has been attempted on this connection. */
    197   boolean isConnected() {
    198     return connected;
    199   }
    200 
    201   /** Returns the route used by this connection. */
    202   public Route getRoute() {
    203     return route;
    204   }
    205 
    206   /**
    207    * Returns the socket that this connection uses, or null if the connection
    208    * is not currently connected.
    209    */
    210   public Socket getSocket() {
    211     return socket;
    212   }
    213 
    214   BufferedSource rawSource() {
    215     if (httpConnection == null) throw new UnsupportedOperationException();
    216     return httpConnection.rawSource();
    217   }
    218 
    219   BufferedSink rawSink() {
    220     if (httpConnection == null) throw new UnsupportedOperationException();
    221     return httpConnection.rawSink();
    222   }
    223 
    224   /** Returns true if this connection is alive. */
    225   boolean isAlive() {
    226     return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown();
    227   }
    228 
    229   /**
    230    * Returns true if we are confident that we can read data from this
    231    * connection. This is more expensive and more accurate than {@link
    232    * #isAlive()}; callers should check {@link #isAlive()} first.
    233    */
    234   boolean isReadable() {
    235     if (httpConnection != null) return httpConnection.isReadable();
    236     return true; // SPDY connections, and connections before connect() are both optimistic.
    237   }
    238 
    239   void resetIdleStartTime() {
    240     if (spdyConnection != null) throw new IllegalStateException("spdyConnection != null");
    241     this.idleStartTimeNs = System.nanoTime();
    242   }
    243 
    244   /** Returns true if this connection is idle. */
    245   boolean isIdle() {
    246     return spdyConnection == null || spdyConnection.isIdle();
    247   }
    248 
    249   /**
    250    * Returns the time in ns when this connection became idle. Undefined if
    251    * this connection is not idle.
    252    */
    253   long getIdleStartTimeNs() {
    254     return spdyConnection == null ? idleStartTimeNs : spdyConnection.getIdleStartTimeNs();
    255   }
    256 
    257   public Handshake getHandshake() {
    258     return handshake;
    259   }
    260 
    261   /** Returns the transport appropriate for this connection. */
    262   Transport newTransport(HttpEngine httpEngine) throws IOException {
    263     return (spdyConnection != null)
    264         ? new SpdyTransport(httpEngine, spdyConnection)
    265         : new HttpTransport(httpEngine, httpConnection);
    266   }
    267 
    268   /**
    269    * Returns true if this is a SPDY connection. Such connections can be used
    270    * in multiple HTTP requests simultaneously.
    271    */
    272   boolean isSpdy() {
    273     return spdyConnection != null;
    274   }
    275 
    276   /**
    277    * Returns the protocol negotiated by this connection, or {@link
    278    * Protocol#HTTP_1_1} if no protocol has been negotiated.
    279    */
    280   public Protocol getProtocol() {
    281     return protocol;
    282   }
    283 
    284   /**
    285    * Sets the protocol negotiated by this connection. Typically this is used
    286    * when an HTTP/1.1 request is sent and an HTTP/1.0 response is received.
    287    */
    288   void setProtocol(Protocol protocol) {
    289     if (protocol == null) throw new IllegalArgumentException("protocol == null");
    290     this.protocol = protocol;
    291   }
    292 
    293   void setTimeouts(int readTimeoutMillis, int writeTimeoutMillis)
    294       throws RouteException {
    295     if (!connected) throw new IllegalStateException("setTimeouts - not connected");
    296 
    297     // Don't set timeouts on shared SPDY connections.
    298     if (httpConnection != null) {
    299       try {
    300         socket.setSoTimeout(readTimeoutMillis);
    301       } catch (IOException e) {
    302         throw new RouteException(e);
    303       }
    304       httpConnection.setTimeouts(readTimeoutMillis, writeTimeoutMillis);
    305     }
    306   }
    307 
    308   void incrementRecycleCount() {
    309     recycleCount++;
    310   }
    311 
    312   /**
    313    * Returns the number of times this connection has been returned to the
    314    * connection pool.
    315    */
    316   int recycleCount() {
    317     return recycleCount;
    318   }
    319 
    320   @Override public String toString() {
    321     return "Connection{"
    322         + route.address.uriHost + ":" + route.address.uriPort
    323         + ", proxy="
    324         + route.proxy
    325         + " hostAddress="
    326         + route.inetSocketAddress.getAddress().getHostAddress()
    327         + " cipherSuite="
    328         + (handshake != null ? handshake.cipherSuite() : "none")
    329         + " protocol="
    330         + protocol
    331         + '}';
    332   }
    333 }
    334