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