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