• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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