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

libcore.net.http.HttpConnection Maven / Gradle / Ivy

/*
 *  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 libcore.net.http;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import libcore.io.IoUtils;
import libcore.net.spdy.SpdyConnection;
import libcore.util.Charsets;
import libcore.util.Libcore;
import libcore.util.Objects;

/**
 * Holds the sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection,
 * which may be used for multiple HTTP request/response exchanges. Connections
 * may be direct to the origin server or via a proxy. Create an instance using
 * the {@link Address} inner class.
 *
 * 

Do not confuse this class with the misnamed {@code HttpURLConnection}, * which isn't so much a connection as a single request/response pair. */ final class HttpConnection { private static final byte[] NPN_PROTOCOLS = new byte[] { 6, 's', 'p', 'd', 'y', '/', '2', 8, 'h', 't', 't', 'p', '/', '1', '.', '1', }; private static final byte[] SPDY2 = new byte[] { 's', 'p', 'd', 'y', '/', '2', }; private static final byte[] HTTP_11 = new byte[] { 'h', 't', 't', 'p', '/', '1', '.', '1', }; private final Address address; private final Socket socket; private InputStream inputStream; private OutputStream outputStream; private SSLSocket sslSocket; private InputStream sslInputStream; private OutputStream sslOutputStream; private boolean recycled = false; private SpdyConnection spdyConnection; /** * The version this client will use. Either 0 for HTTP/1.0, or 1 for * HTTP/1.1. Upon receiving a non-HTTP/1.1 response, this client * automatically sets its version to HTTP/1.0. */ int httpMinorVersion = 1; // Assume HTTP/1.1 private HttpConnection(Address config, int connectTimeout) throws IOException { this.address = config; /* * Try each of the host's addresses for best behavior in mixed IPv4/IPv6 * environments. See http://b/2876927 * TODO: add a hidden method so that Socket.tryAllAddresses can does this for us */ Socket socketCandidate = null; InetAddress[] addresses = InetAddress.getAllByName(config.socketHost); for (int i = 0; i < addresses.length; i++) { socketCandidate = (config.proxy != null && config.proxy.type() != Proxy.Type.HTTP) ? new Socket(config.proxy) : new Socket(); try { socketCandidate.connect( new InetSocketAddress(addresses[i], config.socketPort), connectTimeout); break; } catch (IOException e) { if (i == addresses.length - 1) { throw e; } } } if (socketCandidate == null) { throw new IOException(); } this.socket = socketCandidate; /* * Buffer the socket stream to permit efficient parsing of HTTP headers * and chunk sizes. Benchmarks suggest 128 is sufficient. We cannot * buffer when setting up a tunnel because we may consume bytes intended * for the SSL socket. */ int bufferSize = 128; inputStream = address.requiresTunnel ? socket.getInputStream() : new BufferedInputStream(socket.getInputStream(), bufferSize); outputStream = socket.getOutputStream(); } public static HttpConnection connect(URI uri, SSLSocketFactory sslSocketFactory, Proxy proxy, boolean requiresTunnel, int connectTimeout) throws IOException { /* * Try an explicitly-specified proxy. */ if (proxy != null) { Address address = (proxy.type() == Proxy.Type.DIRECT) ? new Address(uri, sslSocketFactory) : new Address(uri, sslSocketFactory, proxy, requiresTunnel); return HttpConnectionPool.INSTANCE.get(address, connectTimeout); } /* * Try connecting to each of the proxies provided by the ProxySelector * until a connection succeeds. */ ProxySelector selector = ProxySelector.getDefault(); List proxyList = selector.select(uri); if (proxyList != null) { for (Proxy selectedProxy : proxyList) { if (selectedProxy.type() == Proxy.Type.DIRECT) { // the same as NO_PROXY // TODO: if the selector recommends a direct connection, attempt that? continue; } try { Address address = new Address(uri, sslSocketFactory, selectedProxy, requiresTunnel); return HttpConnectionPool.INSTANCE.get(address, connectTimeout); } catch (IOException e) { // failed to connect, tell it to the selector selector.connectFailed(uri, selectedProxy.address(), e); } } } /* * Try a direct connection. If this fails, this method will throw. */ return HttpConnectionPool.INSTANCE.get(new Address(uri, sslSocketFactory), connectTimeout); } public void closeSocketAndStreams() { IoUtils.closeQuietly(sslOutputStream); IoUtils.closeQuietly(sslInputStream); IoUtils.closeQuietly(sslSocket); IoUtils.closeQuietly(outputStream); IoUtils.closeQuietly(inputStream); IoUtils.closeQuietly(socket); } public void setSoTimeout(int readTimeout) throws SocketException { socket.setSoTimeout(readTimeout); } Socket getSocket() { return sslSocket != null ? sslSocket : socket; } public Address getAddress() { return address; } /** * Create an {@code SSLSocket} and perform the SSL handshake * (performing certificate validation. * * @param sslSocketFactory Source of new {@code SSLSocket} instances. * @param tlsTolerant If true, assume server can handle common */ public SSLSocket setupSecureSocket(SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier, boolean tlsTolerant) throws IOException { if (spdyConnection != null || sslOutputStream != null || sslInputStream != null) { throw new IllegalStateException(); } // Create the wrapper over connected socket. sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, address.uriHost, address.uriPort, true /* autoClose */); Libcore.makeTlsTolerant(sslSocket, address.socketHost, tlsTolerant); if (tlsTolerant) { Libcore.setNpnProtocols(sslSocket, NPN_PROTOCOLS); } // Force handshake. This can throw! sslSocket.startHandshake(); // Verify that the socket's certificates are acceptable for the target host. if (!hostnameVerifier.verify(address.uriHost, sslSocket.getSession())) { throw new IOException("Hostname '" + address.uriHost + "' was not verified"); } // SSL success. Prepare to hand out Transport instances. sslOutputStream = sslSocket.getOutputStream(); sslInputStream = sslSocket.getInputStream(); byte[] selectedProtocol; if (tlsTolerant && (selectedProtocol = Libcore.getNpnSelectedProtocol(sslSocket)) != null) { if (Arrays.equals(selectedProtocol, SPDY2)) { spdyConnection = new SpdyConnection.Builder( true, sslInputStream, sslOutputStream).build(); HttpConnectionPool.INSTANCE.share(this); } else if (!Arrays.equals(selectedProtocol, HTTP_11)) { throw new IOException("Unexpected NPN transport " + new String(selectedProtocol, Charsets.ISO_8859_1)); } } return sslSocket; } /** * Return an {@code SSLSocket} if already connected, otherwise null. */ public SSLSocket getSecureSocketIfConnected() { return sslSocket; } /** * Returns true if this connection has been used to satisfy an earlier * HTTP request/response pair. */ public boolean isRecycled() { return recycled; } public void setRecycled() { this.recycled = true; } /** * Returns true if this connection is eligible to be reused for another * request/response pair. */ protected boolean isEligibleForRecycling() { return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown(); } /** * Returns the transport appropriate for this connection. */ public Transport newTransport(HttpEngine httpEngine) throws IOException { if (spdyConnection != null) { return new SpdyTransport(httpEngine, spdyConnection); } else if (sslSocket != null) { return new HttpTransport(httpEngine, sslOutputStream, sslInputStream); } else { return new HttpTransport(httpEngine, outputStream, inputStream); } } /** * Returns true if this is a SPDY connection. Such connections can be used * in multiple HTTP requests simultaneously. */ public boolean isSpdy() { return spdyConnection != null; } /** * This address has two parts: the address we connect to directly and the * origin address of the resource. These are the same unless a proxy is * being used. It also includes the SSL socket factory so that a socket will * not be reused if its SSL configuration is different. */ public static final class Address { private final Proxy proxy; private final boolean requiresTunnel; private final String uriHost; private final int uriPort; private final String socketHost; private final int socketPort; private final SSLSocketFactory sslSocketFactory; public Address(URI uri, SSLSocketFactory sslSocketFactory) throws UnknownHostException { this.proxy = null; this.requiresTunnel = false; this.uriHost = uri.getHost(); this.uriPort = Libcore.getEffectivePort(uri); this.sslSocketFactory = sslSocketFactory; this.socketHost = uriHost; this.socketPort = uriPort; if (uriHost == null) { throw new UnknownHostException(uri.toString()); } } /** * @param requiresTunnel true if the HTTP connection needs to tunnel one * protocol over another, such as when using HTTPS through an HTTP * proxy. When doing so, we must avoid buffering bytes intended for * the higher-level protocol. */ public Address(URI uri, SSLSocketFactory sslSocketFactory, Proxy proxy, boolean requiresTunnel) throws UnknownHostException { this.proxy = proxy; this.requiresTunnel = requiresTunnel; this.uriHost = uri.getHost(); this.uriPort = Libcore.getEffectivePort(uri); this.sslSocketFactory = sslSocketFactory; SocketAddress proxyAddress = proxy.address(); if (!(proxyAddress instanceof InetSocketAddress)) { throw new IllegalArgumentException("Proxy.address() is not an InetSocketAddress: " + proxyAddress.getClass()); } InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress; this.socketHost = proxySocketAddress.getHostName(); this.socketPort = proxySocketAddress.getPort(); if (uriHost == null) { throw new UnknownHostException(uri.toString()); } } public Proxy getProxy() { return proxy; } @Override public boolean equals(Object other) { if (other instanceof Address) { Address that = (Address) other; return Objects.equal(this.proxy, that.proxy) && this.uriHost.equals(that.uriHost) && this.uriPort == that.uriPort && Objects.equal(this.sslSocketFactory, that.sslSocketFactory) && this.requiresTunnel == that.requiresTunnel; } return false; } @Override public int hashCode() { int result = 17; result = 31 * result + uriHost.hashCode(); result = 31 * result + uriPort; result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0); result = 31 * result + (proxy != null ? proxy.hashCode() : 0); result = 31 * result + (requiresTunnel ? 1 : 0); return result; } public HttpConnection connect(int connectTimeout) throws IOException { return new HttpConnection(this, connectTimeout); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy