All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.squareup.okhttp.Connection Maven / Gradle / Ivy

There is a newer version: 2.7.5
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.squareup.okhttp;

import com.squareup.okhttp.internal.ConnectionSpecSelector;
import com.squareup.okhttp.internal.Platform;
import com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.framed.FramedConnection;
import com.squareup.okhttp.internal.http.FramedTransport;
import com.squareup.okhttp.internal.http.HttpConnection;
import com.squareup.okhttp.internal.http.HttpEngine;
import com.squareup.okhttp.internal.http.HttpTransport;
import com.squareup.okhttp.internal.http.OkHeaders;
import com.squareup.okhttp.internal.http.RouteException;
import com.squareup.okhttp.internal.http.Transport;
import com.squareup.okhttp.internal.tls.OkHostnameVerifier;
import java.io.IOException;
import java.net.Proxy;
import java.net.Socket;
import java.net.UnknownServiceException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import okio.BufferedSink;
import okio.BufferedSource;
import okio.Source;

import static com.squareup.okhttp.internal.Util.closeQuietly;
import static java.net.HttpURLConnection.HTTP_OK;
import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;

/**
 * The sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection. May be
 * used for multiple HTTP request/response exchanges. Connections may be direct
 * to the origin server or via a proxy.
 *
 * 

Typically instances of this class are created, connected and exercised * automatically by the HTTP client. Applications may use this class to monitor * HTTP connections as members of a {@linkplain ConnectionPool connection pool}. * *

Do not confuse this class with the misnamed {@code HttpURLConnection}, * which isn't so much a connection as a single request/response exchange. * *

Modern TLS

* There are tradeoffs when selecting which options to include when negotiating * a secure connection to a remote host. Newer TLS options are quite useful: *
    *
  • Server Name Indication (SNI) enables one IP address to negotiate secure * connections for multiple domain names. *
  • Application Layer Protocol Negotiation (ALPN) enables the HTTPS port * (443) to be used for different HTTP and SPDY protocols. *
* Unfortunately, older HTTPS servers refuse to connect when such options are * presented. Rather than avoiding these options entirely, this class allows a * connection to be attempted with modern options and then retried without them * should the attempt fail. */ public final class Connection { private final ConnectionPool pool; private final Route route; private Socket socket; private boolean connected = false; private HttpConnection httpConnection; private FramedConnection framedConnection; private Protocol protocol = Protocol.HTTP_1_1; private long idleStartTimeNs; private Handshake handshake; private int recycleCount; /** * The object that owns this connection. Null if it is shared (for SPDY), * belongs to a pool, or has been discarded. Guarded by {@code pool}, which * clears the owner when an incoming connection is recycled. */ private Object owner; public Connection(ConnectionPool pool, Route route) { this.pool = pool; this.route = route; } Object getOwner() { synchronized (pool) { return owner; } } void setOwner(Object owner) { if (isFramed()) return; // Framed connections are shared. synchronized (pool) { if (this.owner != null) throw new IllegalStateException("Connection already has an owner!"); this.owner = owner; } } /** * Attempts to clears the owner of this connection. Returns true if the owner * was cleared and the connection can be pooled or reused. This will return * false if the connection cannot be pooled or reused, such as if it was * closed with {@link #closeIfOwnedBy}. */ boolean clearOwner() { synchronized (pool) { if (owner == null) { // No owner? Don't reuse this connection. return false; } owner = null; return true; } } /** * Closes this connection if it is currently owned by {@code owner}. This also * strips the ownership of the connection so it cannot be pooled or reused. */ void closeIfOwnedBy(Object owner) throws IOException { if (isFramed()) throw new IllegalStateException(); synchronized (pool) { if (this.owner != owner) { return; // Wrong owner. Perhaps a late disconnect? } this.owner = null; // Drop the owner so the connection won't be reused. } // Don't close() inside the synchronized block. if (socket != null) { socket.close(); } } void connect(int connectTimeout, int readTimeout, int writeTimeout, Request request, List connectionSpecs, boolean connectionRetryEnabled) throws RouteException { if (connected) throw new IllegalStateException("already connected"); RouteException routeException = null; ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs); Proxy proxy = route.getProxy(); Address address = route.getAddress(); if (route.address.getSslSocketFactory() == null && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) { throw new RouteException(new UnknownServiceException( "CLEARTEXT communication not supported: " + connectionSpecs)); } while (!connected) { try { socket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP ? address.getSocketFactory().createSocket() : new Socket(proxy); connectSocket(connectTimeout, readTimeout, writeTimeout, request, connectionSpecSelector); connected = true; // Success! } catch (IOException e) { Util.closeQuietly(socket); socket = null; if (routeException == null) { routeException = new RouteException(e); } else { routeException.addConnectException(e); } if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) { throw routeException; } } } } /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */ private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout, Request request, ConnectionSpecSelector connectionSpecSelector) throws IOException { socket.setSoTimeout(readTimeout); Platform.get().connectSocket(socket, route.getSocketAddress(), connectTimeout); if (route.address.getSslSocketFactory() != null) { connectTls(readTimeout, writeTimeout, request, connectionSpecSelector); } if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) { socket.setSoTimeout(0); // Framed connection timeouts are set per-stream. framedConnection = new FramedConnection.Builder(route.address.uriHost, true, socket) .protocol(protocol).build(); framedConnection.sendConnectionPreface(); } else { httpConnection = new HttpConnection(pool, this, socket); } } private void connectTls(int readTimeout, int writeTimeout, Request request, ConnectionSpecSelector connectionSpecSelector) throws IOException { if (route.requiresTunnel()) { createTunnel(readTimeout, writeTimeout, request); } Address address = route.getAddress(); SSLSocketFactory sslSocketFactory = address.getSslSocketFactory(); boolean success = false; SSLSocket sslSocket = null; try { // Create the wrapper over the connected socket. sslSocket = (SSLSocket) sslSocketFactory.createSocket( socket, address.getUriHost(), address.getUriPort(), true /* autoClose */); // Configure the socket's ciphers, TLS versions, and extensions. ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket); if (connectionSpec.supportsTlsExtensions()) { Platform.get().configureTlsExtensions( sslSocket, address.getUriHost(), address.getProtocols()); } // Force handshake. This can throw! sslSocket.startHandshake(); Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession()); // Verify that the socket's certificates are acceptable for the target host. if (!address.getHostnameVerifier().verify(address.getUriHost(), sslSocket.getSession())) { X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0); throw new SSLPeerUnverifiedException("Hostname " + address.getUriHost() + " not verified:" + "\n certificate: " + CertificatePinner.pin(cert) + "\n DN: " + cert.getSubjectDN().getName() + "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert)); } // Check that the certificate pinner is satisfied by the certificates presented. address.getCertificatePinner().check(address.getUriHost(), unverifiedHandshake.peerCertificates()); // Success! Save the handshake and the ALPN protocol. String maybeProtocol = connectionSpec.supportsTlsExtensions() ? Platform.get().getSelectedProtocol(sslSocket) : null; protocol = maybeProtocol != null ? Protocol.get(maybeProtocol) : Protocol.HTTP_1_1; handshake = unverifiedHandshake; socket = sslSocket; success = true; } catch (AssertionError e) { if (Util.isAndroidGetsocknameError(e)) throw new IOException(e); throw e; } finally { if (sslSocket != null) { Platform.get().afterHandshake(sslSocket); } if (!success) { closeQuietly(sslSocket); } } } /** * To make an HTTPS connection over an HTTP proxy, send an unencrypted * CONNECT request to create the proxy connection. This may need to be * retried if the proxy requires authorization. */ private void createTunnel(int readTimeout, int writeTimeout, Request request) throws IOException { // Make an SSL Tunnel on the first message pair of each SSL + proxy connection. Request tunnelRequest = createTunnelRequest(request); HttpConnection tunnelConnection = new HttpConnection(pool, this, socket); tunnelConnection.setTimeouts(readTimeout, writeTimeout); HttpUrl url = tunnelRequest.httpUrl(); String requestLine = "CONNECT " + url.host() + ":" + url.port() + " HTTP/1.1"; while (true) { tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine); tunnelConnection.flush(); Response response = tunnelConnection.readResponse().request(tunnelRequest).build(); // The response body from a CONNECT should be empty, but if it is not then we should consume // it before proceeding. long contentLength = OkHeaders.contentLength(response); if (contentLength == -1L) { contentLength = 0L; } Source body = tunnelConnection.newFixedLengthSource(contentLength); Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS); body.close(); switch (response.code()) { case HTTP_OK: // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If // that happens, then we will have buffered bytes that are needed by the SSLSocket! // This check is imperfect: it doesn't tell us whether a handshake will succeed, just // that it will almost certainly fail because the proxy has sent unexpected data. if (tunnelConnection.bufferSize() > 0) { throw new IOException("TLS tunnel buffered too many bytes!"); } return; case HTTP_PROXY_AUTH: tunnelRequest = OkHeaders.processAuthHeader( route.getAddress().getAuthenticator(), response, route.getProxy()); if (tunnelRequest != null) continue; throw new IOException("Failed to authenticate with proxy"); default: throw new IOException( "Unexpected response code for CONNECT: " + response.code()); } } } /** * Returns a request that creates a TLS tunnel via an HTTP proxy, or null if * no tunnel is necessary. Everything in the tunnel request is sent * unencrypted to the proxy server, so tunnels include only the minimum set of * headers. This avoids sending potentially sensitive data like HTTP cookies * to the proxy unencrypted. */ private Request createTunnelRequest(Request request) throws IOException { HttpUrl tunnelUrl = new HttpUrl.Builder() .scheme("https") .host(request.httpUrl().host()) .port(request.httpUrl().port()) .build(); Request.Builder result = new Request.Builder() .url(tunnelUrl) .header("Host", Util.hostHeader(tunnelUrl)) .header("Proxy-Connection", "Keep-Alive"); // For HTTP/1.0 proxies like Squid. // Copy over the User-Agent header if it exists. String userAgent = request.header("User-Agent"); if (userAgent != null) { result.header("User-Agent", userAgent); } // Copy over the Proxy-Authorization header if it exists. String proxyAuthorization = request.header("Proxy-Authorization"); if (proxyAuthorization != null) { result.header("Proxy-Authorization", proxyAuthorization); } return result.build(); } /** * Connects this connection if it isn't already. This creates tunnels, shares * the connection with the connection pool, and configures timeouts. */ void connectAndSetOwner(OkHttpClient client, Object owner, Request request) throws RouteException { setOwner(owner); if (!isConnected()) { List connectionSpecs = route.address.getConnectionSpecs(); connect(client.getConnectTimeout(), client.getReadTimeout(), client.getWriteTimeout(), request, connectionSpecs, client.getRetryOnConnectionFailure()); if (isFramed()) { client.getConnectionPool().share(this); } client.routeDatabase().connected(getRoute()); } setTimeouts(client.getReadTimeout(), client.getWriteTimeout()); } /** Returns true if {@link #connect} has been attempted on this connection. */ boolean isConnected() { return connected; } /** Returns the route used by this connection. */ public Route getRoute() { return route; } /** * Returns the socket that this connection uses, or null if the connection * is not currently connected. */ public Socket getSocket() { return socket; } BufferedSource rawSource() { if (httpConnection == null) throw new UnsupportedOperationException(); return httpConnection.rawSource(); } BufferedSink rawSink() { if (httpConnection == null) throw new UnsupportedOperationException(); return httpConnection.rawSink(); } /** Returns true if this connection is alive. */ boolean isAlive() { return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown(); } /** * Returns true if we are confident that we can read data from this * connection. This is more expensive and more accurate than {@link * #isAlive()}; callers should check {@link #isAlive()} first. */ boolean isReadable() { if (httpConnection != null) return httpConnection.isReadable(); return true; // Framed connections, and connections before connect() are both optimistic. } void resetIdleStartTime() { if (framedConnection != null) throw new IllegalStateException("framedConnection != null"); this.idleStartTimeNs = System.nanoTime(); } /** Returns true if this connection is idle. */ boolean isIdle() { return framedConnection == null || framedConnection.isIdle(); } /** * Returns the time in ns when this connection became idle. Undefined if * this connection is not idle. */ long getIdleStartTimeNs() { return framedConnection == null ? idleStartTimeNs : framedConnection.getIdleStartTimeNs(); } public Handshake getHandshake() { return handshake; } /** Returns the transport appropriate for this connection. */ Transport newTransport(HttpEngine httpEngine) throws IOException { return (framedConnection != null) ? new FramedTransport(httpEngine, framedConnection) : new HttpTransport(httpEngine, httpConnection); } /** * Returns true if this is a SPDY connection. Such connections can be used * in multiple HTTP requests simultaneously. */ boolean isFramed() { return framedConnection != null; } /** * Returns the protocol negotiated by this connection, or {@link * Protocol#HTTP_1_1} if no protocol has been negotiated. */ public Protocol getProtocol() { return protocol; } /** * Sets the protocol negotiated by this connection. Typically this is used * when an HTTP/1.1 request is sent and an HTTP/1.0 response is received. */ void setProtocol(Protocol protocol) { if (protocol == null) throw new IllegalArgumentException("protocol == null"); this.protocol = protocol; } void setTimeouts(int readTimeoutMillis, int writeTimeoutMillis) throws RouteException { if (!connected) throw new IllegalStateException("setTimeouts - not connected"); // Don't set timeouts on shared SPDY connections. if (httpConnection != null) { try { socket.setSoTimeout(readTimeoutMillis); } catch (IOException e) { throw new RouteException(e); } httpConnection.setTimeouts(readTimeoutMillis, writeTimeoutMillis); } } void incrementRecycleCount() { recycleCount++; } /** * Returns the number of times this connection has been returned to the * connection pool. */ int recycleCount() { return recycleCount; } @Override public String toString() { return "Connection{" + route.address.uriHost + ":" + route.address.uriPort + ", proxy=" + route.proxy + " hostAddress=" + route.inetSocketAddress.getAddress().getHostAddress() + " cipherSuite=" + (handshake != null ? handshake.cipherSuite() : "none") + " protocol=" + protocol + '}'; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy