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.ConnectionSpecSelector; 20 import com.squareup.okhttp.internal.Platform; 21 import com.squareup.okhttp.internal.Util; 22 import com.squareup.okhttp.internal.framed.FramedConnection; 23 import com.squareup.okhttp.internal.http.FramedTransport; 24 import com.squareup.okhttp.internal.http.HttpConnection; 25 import com.squareup.okhttp.internal.http.HttpEngine; 26 import com.squareup.okhttp.internal.http.HttpTransport; 27 import com.squareup.okhttp.internal.http.OkHeaders; 28 import com.squareup.okhttp.internal.http.RouteException; 29 import com.squareup.okhttp.internal.http.Transport; 30 import com.squareup.okhttp.internal.tls.OkHostnameVerifier; 31 import java.io.IOException; 32 import java.net.Proxy; 33 import java.net.Socket; 34 import java.net.UnknownServiceException; 35 import java.security.cert.X509Certificate; 36 import java.util.List; 37 import java.util.concurrent.TimeUnit; 38 import javax.net.ssl.SSLPeerUnverifiedException; 39 import javax.net.ssl.SSLSocket; 40 import javax.net.ssl.SSLSocketFactory; 41 import okio.BufferedSink; 42 import okio.BufferedSource; 43 import okio.Source; 44 45 import static com.squareup.okhttp.internal.Util.closeQuietly; 46 import static java.net.HttpURLConnection.HTTP_OK; 47 import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; 48 49 /** 50 * The sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection. May be 51 * used for multiple HTTP request/response exchanges. Connections may be direct 52 * to the origin server or via a proxy. 53 * 54 * <p>Typically instances of this class are created, connected and exercised 55 * automatically by the HTTP client. Applications may use this class to monitor 56 * HTTP connections as members of a {@linkplain ConnectionPool connection pool}. 57 * 58 * <p>Do not confuse this class with the misnamed {@code HttpURLConnection}, 59 * which isn't so much a connection as a single request/response exchange. 60 * 61 * <h3>Modern TLS</h3> 62 * There are tradeoffs when selecting which options to include when negotiating 63 * a secure connection to a remote host. Newer TLS options are quite useful: 64 * <ul> 65 * <li>Server Name Indication (SNI) enables one IP address to negotiate secure 66 * connections for multiple domain names. 67 * <li>Application Layer Protocol Negotiation (ALPN) enables the HTTPS port 68 * (443) to be used for different HTTP and SPDY protocols. 69 * </ul> 70 * Unfortunately, older HTTPS servers refuse to connect when such options are 71 * presented. Rather than avoiding these options entirely, this class allows a 72 * connection to be attempted with modern options and then retried without them 73 * should the attempt fail. 74 */ 75 public final class Connection { 76 private final ConnectionPool pool; 77 private final Route route; 78 79 private Socket socket; 80 private boolean connected = false; 81 private HttpConnection httpConnection; 82 private FramedConnection framedConnection; 83 private Protocol protocol = Protocol.HTTP_1_1; 84 private long idleStartTimeNs; 85 private Handshake handshake; 86 private int recycleCount; 87 88 /** 89 * The object that owns this connection. Null if it is shared (for SPDY), 90 * belongs to a pool, or has been discarded. Guarded by {@code pool}, which 91 * clears the owner when an incoming connection is recycled. 92 */ 93 private Object owner; 94 Connection(ConnectionPool pool, Route route)95 public Connection(ConnectionPool pool, Route route) { 96 this.pool = pool; 97 this.route = route; 98 } 99 getOwner()100 Object getOwner() { 101 synchronized (pool) { 102 return owner; 103 } 104 } 105 setOwner(Object owner)106 void setOwner(Object owner) { 107 if (isFramed()) return; // Framed connections are shared. 108 synchronized (pool) { 109 if (this.owner != null) throw new IllegalStateException("Connection already has an owner!"); 110 this.owner = owner; 111 } 112 } 113 114 /** 115 * Attempts to clears the owner of this connection. Returns true if the owner 116 * was cleared and the connection can be pooled or reused. This will return 117 * false if the connection cannot be pooled or reused, such as if it was 118 * closed with {@link #closeIfOwnedBy}. 119 */ clearOwner()120 boolean clearOwner() { 121 synchronized (pool) { 122 if (owner == null) { 123 // No owner? Don't reuse this connection. 124 return false; 125 } 126 127 owner = null; 128 return true; 129 } 130 } 131 132 /** 133 * Closes this connection if it is currently owned by {@code owner}. This also 134 * strips the ownership of the connection so it cannot be pooled or reused. 135 */ closeIfOwnedBy(Object owner)136 void closeIfOwnedBy(Object owner) throws IOException { 137 if (isFramed()) throw new IllegalStateException(); 138 synchronized (pool) { 139 if (this.owner != owner) { 140 return; // Wrong owner. Perhaps a late disconnect? 141 } 142 143 this.owner = null; // Drop the owner so the connection won't be reused. 144 } 145 146 // Don't close() inside the synchronized block. 147 if (socket != null) { 148 socket.close(); 149 } 150 } 151 connect(int connectTimeout, int readTimeout, int writeTimeout, Request request, List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled)152 void connect(int connectTimeout, int readTimeout, int writeTimeout, Request request, 153 List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException { 154 if (connected) throw new IllegalStateException("already connected"); 155 156 RouteException routeException = null; 157 ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs); 158 Proxy proxy = route.getProxy(); 159 Address address = route.getAddress(); 160 161 if (route.address.getSslSocketFactory() == null 162 && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) { 163 throw new RouteException(new UnknownServiceException( 164 "CLEARTEXT communication not supported: " + connectionSpecs)); 165 } 166 167 while (!connected) { 168 try { 169 socket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP 170 ? address.getSocketFactory().createSocket() 171 : new Socket(proxy); 172 connectSocket(connectTimeout, readTimeout, writeTimeout, request, 173 connectionSpecSelector); 174 connected = true; // Success! 175 } catch (IOException e) { 176 Util.closeQuietly(socket); 177 socket = null; 178 179 if (routeException == null) { 180 routeException = new RouteException(e); 181 } else { 182 routeException.addConnectException(e); 183 } 184 185 if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) { 186 throw routeException; 187 } 188 } 189 } 190 } 191 192 /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */ connectSocket(int connectTimeout, int readTimeout, int writeTimeout, Request request, ConnectionSpecSelector connectionSpecSelector)193 private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout, 194 Request request, ConnectionSpecSelector connectionSpecSelector) throws IOException { 195 socket.setSoTimeout(readTimeout); 196 Platform.get().connectSocket(socket, route.getSocketAddress(), connectTimeout); 197 198 if (route.address.getSslSocketFactory() != null) { 199 connectTls(readTimeout, writeTimeout, request, connectionSpecSelector); 200 } 201 202 if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) { 203 socket.setSoTimeout(0); // Framed connection timeouts are set per-stream. 204 framedConnection = new FramedConnection.Builder(route.address.uriHost, true, socket) 205 .protocol(protocol).build(); 206 framedConnection.sendConnectionPreface(); 207 } else { 208 httpConnection = new HttpConnection(pool, this, socket); 209 } 210 } 211 connectTls(int readTimeout, int writeTimeout, Request request, ConnectionSpecSelector connectionSpecSelector)212 private void connectTls(int readTimeout, int writeTimeout, Request request, 213 ConnectionSpecSelector connectionSpecSelector) throws IOException { 214 if (route.requiresTunnel()) { 215 createTunnel(readTimeout, writeTimeout, request); 216 } 217 218 Address address = route.getAddress(); 219 SSLSocketFactory sslSocketFactory = address.getSslSocketFactory(); 220 boolean success = false; 221 SSLSocket sslSocket = null; 222 try { 223 // Create the wrapper over the connected socket. 224 sslSocket = (SSLSocket) sslSocketFactory.createSocket( 225 socket, address.getRfc2732Host(), address.getUriPort(), true /* autoClose */); 226 227 // Configure the socket's ciphers, TLS versions, and extensions. 228 ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket); 229 if (connectionSpec.supportsTlsExtensions()) { 230 Platform.get().configureTlsExtensions( 231 sslSocket, address.getRfc2732Host(), address.getProtocols()); 232 } 233 234 // Force handshake. This can throw! 235 sslSocket.startHandshake(); 236 Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession()); 237 238 // Verify that the socket's certificates are acceptable for the target host. 239 if (!address.getHostnameVerifier().verify(address.getRfc2732Host(), sslSocket.getSession())) { 240 X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0); 241 throw new SSLPeerUnverifiedException("Hostname " + address.getRfc2732Host() + " not verified:" 242 + "\n certificate: " + CertificatePinner.pin(cert) 243 + "\n DN: " + cert.getSubjectDN().getName() 244 + "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert)); 245 } 246 247 // Check that the certificate pinner is satisfied by the certificates presented. 248 address.getCertificatePinner().check(address.getRfc2732Host(), 249 unverifiedHandshake.peerCertificates()); 250 251 // Success! Save the handshake and the ALPN protocol. 252 String maybeProtocol = connectionSpec.supportsTlsExtensions() 253 ? Platform.get().getSelectedProtocol(sslSocket) 254 : null; 255 protocol = maybeProtocol != null 256 ? Protocol.get(maybeProtocol) 257 : Protocol.HTTP_1_1; 258 handshake = unverifiedHandshake; 259 socket = sslSocket; 260 success = true; 261 } catch (AssertionError e) { 262 if (Util.isAndroidGetsocknameError(e)) throw new IOException(e); 263 throw e; 264 } finally { 265 if (sslSocket != null) { 266 Platform.get().afterHandshake(sslSocket); 267 } 268 if (!success) { 269 closeQuietly(sslSocket); 270 } 271 } 272 } 273 274 /** 275 * To make an HTTPS connection over an HTTP proxy, send an unencrypted 276 * CONNECT request to create the proxy connection. This may need to be 277 * retried if the proxy requires authorization. 278 */ createTunnel(int readTimeout, int writeTimeout, Request request)279 private void createTunnel(int readTimeout, int writeTimeout, Request request) throws IOException { 280 // Make an SSL Tunnel on the first message pair of each SSL + proxy connection. 281 Request tunnelRequest = createTunnelRequest(request); 282 HttpConnection tunnelConnection = new HttpConnection(pool, this, socket); 283 tunnelConnection.setTimeouts(readTimeout, writeTimeout); 284 HttpUrl url = tunnelRequest.httpUrl(); 285 String requestLine = "CONNECT " + url.rfc2732host() + ":" + url.port() + " HTTP/1.1"; 286 while (true) { 287 tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine); 288 tunnelConnection.flush(); 289 Response response = tunnelConnection.readResponse().request(tunnelRequest).build(); 290 // The response body from a CONNECT should be empty, but if it is not then we should consume 291 // it before proceeding. 292 long contentLength = OkHeaders.contentLength(response); 293 if (contentLength == -1L) { 294 contentLength = 0L; 295 } 296 Source body = tunnelConnection.newFixedLengthSource(contentLength); 297 Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS); 298 body.close(); 299 300 switch (response.code()) { 301 case HTTP_OK: 302 // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If 303 // that happens, then we will have buffered bytes that are needed by the SSLSocket! 304 // This check is imperfect: it doesn't tell us whether a handshake will succeed, just 305 // that it will almost certainly fail because the proxy has sent unexpected data. 306 if (tunnelConnection.bufferSize() > 0) { 307 throw new IOException("TLS tunnel buffered too many bytes!"); 308 } 309 return; 310 311 case HTTP_PROXY_AUTH: 312 tunnelRequest = OkHeaders.processAuthHeader( 313 route.getAddress().getAuthenticator(), response, route.getProxy()); 314 if (tunnelRequest != null) continue; 315 throw new IOException("Failed to authenticate with proxy"); 316 317 default: 318 throw new IOException( 319 "Unexpected response code for CONNECT: " + response.code()); 320 } 321 } 322 } 323 324 /** 325 * Returns a request that creates a TLS tunnel via an HTTP proxy, or null if 326 * no tunnel is necessary. Everything in the tunnel request is sent 327 * unencrypted to the proxy server, so tunnels include only the minimum set of 328 * headers. This avoids sending potentially sensitive data like HTTP cookies 329 * to the proxy unencrypted. 330 */ createTunnelRequest(Request request)331 private Request createTunnelRequest(Request request) throws IOException { 332 HttpUrl tunnelUrl = new HttpUrl.Builder() 333 .scheme("https") 334 .host(request.httpUrl().host()) 335 .port(request.httpUrl().port()) 336 .build(); 337 Request.Builder result = new Request.Builder() 338 .url(tunnelUrl) 339 .header("Host", Util.hostHeader(tunnelUrl)) 340 .header("Proxy-Connection", "Keep-Alive"); // For HTTP/1.0 proxies like Squid. 341 342 // Copy over the User-Agent header if it exists. 343 String userAgent = request.header("User-Agent"); 344 if (userAgent != null) { 345 result.header("User-Agent", userAgent); 346 } 347 348 // Copy over the Proxy-Authorization header if it exists. 349 String proxyAuthorization = request.header("Proxy-Authorization"); 350 if (proxyAuthorization != null) { 351 result.header("Proxy-Authorization", proxyAuthorization); 352 } 353 354 return result.build(); 355 } 356 357 /** 358 * Connects this connection if it isn't already. This creates tunnels, shares 359 * the connection with the connection pool, and configures timeouts. 360 */ connectAndSetOwner(OkHttpClient client, Object owner, Request request)361 void connectAndSetOwner(OkHttpClient client, Object owner, Request request) 362 throws RouteException { 363 setOwner(owner); 364 365 if (!isConnected()) { 366 List<ConnectionSpec> connectionSpecs = route.address.getConnectionSpecs(); 367 connect(client.getConnectTimeout(), client.getReadTimeout(), client.getWriteTimeout(), 368 request, connectionSpecs, client.getRetryOnConnectionFailure()); 369 if (isFramed()) { 370 client.getConnectionPool().share(this); 371 } 372 client.routeDatabase().connected(getRoute()); 373 } 374 375 setTimeouts(client.getReadTimeout(), client.getWriteTimeout()); 376 } 377 378 /** Returns true if {@link #connect} has been attempted on this connection. */ isConnected()379 boolean isConnected() { 380 return connected; 381 } 382 383 /** Returns the route used by this connection. */ getRoute()384 public Route getRoute() { 385 return route; 386 } 387 388 /** 389 * Returns the socket that this connection uses, or null if the connection 390 * is not currently connected. 391 */ getSocket()392 public Socket getSocket() { 393 return socket; 394 } 395 rawSource()396 BufferedSource rawSource() { 397 if (httpConnection == null) throw new UnsupportedOperationException(); 398 return httpConnection.rawSource(); 399 } 400 rawSink()401 BufferedSink rawSink() { 402 if (httpConnection == null) throw new UnsupportedOperationException(); 403 return httpConnection.rawSink(); 404 } 405 406 /** Returns true if this connection is alive. */ isAlive()407 boolean isAlive() { 408 return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown(); 409 } 410 411 /** 412 * Returns true if we are confident that we can read data from this 413 * connection. This is more expensive and more accurate than {@link 414 * #isAlive()}; callers should check {@link #isAlive()} first. 415 */ isReadable()416 boolean isReadable() { 417 if (httpConnection != null) return httpConnection.isReadable(); 418 return true; // Framed connections, and connections before connect() are both optimistic. 419 } 420 resetIdleStartTime()421 void resetIdleStartTime() { 422 if (framedConnection != null) throw new IllegalStateException("framedConnection != null"); 423 this.idleStartTimeNs = System.nanoTime(); 424 } 425 426 /** Returns true if this connection is idle. */ isIdle()427 boolean isIdle() { 428 return framedConnection == null || framedConnection.isIdle(); 429 } 430 431 /** 432 * Returns the time in ns when this connection became idle. Undefined if 433 * this connection is not idle. 434 */ getIdleStartTimeNs()435 long getIdleStartTimeNs() { 436 return framedConnection == null ? idleStartTimeNs : framedConnection.getIdleStartTimeNs(); 437 } 438 getHandshake()439 public Handshake getHandshake() { 440 return handshake; 441 } 442 443 /** Returns the transport appropriate for this connection. */ newTransport(HttpEngine httpEngine)444 Transport newTransport(HttpEngine httpEngine) throws IOException { 445 return (framedConnection != null) 446 ? new FramedTransport(httpEngine, framedConnection) 447 : new HttpTransport(httpEngine, httpConnection); 448 } 449 450 /** 451 * Returns true if this is a SPDY connection. Such connections can be used 452 * in multiple HTTP requests simultaneously. 453 */ isFramed()454 boolean isFramed() { 455 return framedConnection != null; 456 } 457 458 /** 459 * Returns the protocol negotiated by this connection, or {@link 460 * Protocol#HTTP_1_1} if no protocol has been negotiated. 461 */ getProtocol()462 public Protocol getProtocol() { 463 return protocol; 464 } 465 466 /** 467 * Sets the protocol negotiated by this connection. Typically this is used 468 * when an HTTP/1.1 request is sent and an HTTP/1.0 response is received. 469 */ setProtocol(Protocol protocol)470 void setProtocol(Protocol protocol) { 471 if (protocol == null) throw new IllegalArgumentException("protocol == null"); 472 this.protocol = protocol; 473 } 474 setTimeouts(int readTimeoutMillis, int writeTimeoutMillis)475 void setTimeouts(int readTimeoutMillis, int writeTimeoutMillis) 476 throws RouteException { 477 if (!connected) throw new IllegalStateException("setTimeouts - not connected"); 478 479 // Don't set timeouts on shared SPDY connections. 480 if (httpConnection != null) { 481 try { 482 socket.setSoTimeout(readTimeoutMillis); 483 } catch (IOException e) { 484 throw new RouteException(e); 485 } 486 httpConnection.setTimeouts(readTimeoutMillis, writeTimeoutMillis); 487 } 488 } 489 incrementRecycleCount()490 void incrementRecycleCount() { 491 recycleCount++; 492 } 493 494 /** 495 * Returns the number of times this connection has been returned to the 496 * connection pool. 497 */ recycleCount()498 int recycleCount() { 499 return recycleCount; 500 } 501 toString()502 @Override public String toString() { 503 return "Connection{" 504 + route.address.uriHost + ":" + route.address.uriPort 505 + ", proxy=" 506 + route.proxy 507 + " hostAddress=" 508 + route.inetSocketAddress.getAddress().getHostAddress() 509 + " cipherSuite=" 510 + (handshake != null ? handshake.cipherSuite() : "none") 511 + " protocol=" 512 + protocol 513 + '}'; 514 } 515 } 516