• 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 
18 package libcore.net.http;
19 
20 import java.io.BufferedInputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.net.InetAddress;
25 import java.net.InetSocketAddress;
26 import java.net.Proxy;
27 import java.net.ProxySelector;
28 import java.net.Socket;
29 import java.net.SocketAddress;
30 import java.net.SocketException;
31 import java.net.URI;
32 import java.net.UnknownHostException;
33 import java.util.List;
34 import javax.net.ssl.HostnameVerifier;
35 import javax.net.ssl.SSLSocket;
36 import javax.net.ssl.SSLSocketFactory;
37 import libcore.io.IoUtils;
38 import libcore.util.Objects;
39 import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
40 
41 /**
42  * Holds the sockets and streams of an HTTP or HTTPS connection, which may be
43  * used for multiple HTTP request/response exchanges. Connections may be direct
44  * to the origin server or via a proxy. Create an instance using the {@link
45  * Address} inner class.
46  *
47  * <p>Do not confuse this class with the misnamed {@code HttpURLConnection},
48  * which isn't so much a connection as a single request/response pair.
49  */
50 final class HttpConnection {
51     private final Address address;
52     private final Socket socket;
53     private InputStream inputStream;
54     private OutputStream outputStream;
55     private SSLSocket unverifiedSocket;
56     private SSLSocket sslSocket;
57     private InputStream sslInputStream;
58     private OutputStream sslOutputStream;
59     private boolean recycled = false;
60 
HttpConnection(Address config, int connectTimeout)61     private HttpConnection(Address config, int connectTimeout) throws IOException {
62         this.address = config;
63 
64         /*
65          * Try each of the host's addresses for best behavior in mixed IPv4/IPv6
66          * environments. See http://b/2876927
67          * TODO: add a hidden method so that Socket.tryAllAddresses can does this for us
68          */
69         Socket socketCandidate = null;
70         InetAddress[] addresses = InetAddress.getAllByName(config.socketHost);
71         for (int i = 0; i < addresses.length; i++) {
72             socketCandidate = (config.proxy != null && config.proxy.type() != Proxy.Type.HTTP)
73                     ? new Socket(config.proxy)
74                     : new Socket();
75             try {
76                 socketCandidate.connect(
77                         new InetSocketAddress(addresses[i], config.socketPort), connectTimeout);
78                 break;
79             } catch (IOException e) {
80                 if (i == addresses.length - 1) {
81                     throw e;
82                 }
83             }
84         }
85 
86         this.socket = socketCandidate;
87     }
88 
connect(URI uri, SSLSocketFactory sslSocketFactory, Proxy proxy, boolean requiresTunnel, int connectTimeout)89     public static HttpConnection connect(URI uri, SSLSocketFactory sslSocketFactory,
90             Proxy proxy, boolean requiresTunnel, int connectTimeout) throws IOException {
91         /*
92          * Try an explicitly-specified proxy.
93          */
94         if (proxy != null) {
95             Address address = (proxy.type() == Proxy.Type.DIRECT)
96                     ? new Address(uri, sslSocketFactory)
97                     : new Address(uri, sslSocketFactory, proxy, requiresTunnel);
98             return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
99         }
100 
101         /*
102          * Try connecting to each of the proxies provided by the ProxySelector
103          * until a connection succeeds.
104          */
105         ProxySelector selector = ProxySelector.getDefault();
106         List<Proxy> proxyList = selector.select(uri);
107         if (proxyList != null) {
108             for (Proxy selectedProxy : proxyList) {
109                 if (selectedProxy.type() == Proxy.Type.DIRECT) {
110                     // the same as NO_PROXY
111                     // TODO: if the selector recommends a direct connection, attempt that?
112                     continue;
113                 }
114                 try {
115                     Address address = new Address(uri, sslSocketFactory,
116                             selectedProxy, requiresTunnel);
117                     return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
118                 } catch (IOException e) {
119                     // failed to connect, tell it to the selector
120                     selector.connectFailed(uri, selectedProxy.address(), e);
121                 }
122             }
123         }
124 
125         /*
126          * Try a direct connection. If this fails, this method will throw.
127          */
128         return HttpConnectionPool.INSTANCE.get(new Address(uri, sslSocketFactory), connectTimeout);
129     }
130 
closeSocketAndStreams()131     public void closeSocketAndStreams() {
132         IoUtils.closeQuietly(sslOutputStream);
133         IoUtils.closeQuietly(sslInputStream);
134         IoUtils.closeQuietly(sslSocket);
135         IoUtils.closeQuietly(outputStream);
136         IoUtils.closeQuietly(inputStream);
137         IoUtils.closeQuietly(socket);
138     }
139 
setSoTimeout(int readTimeout)140     public void setSoTimeout(int readTimeout) throws SocketException {
141         socket.setSoTimeout(readTimeout);
142     }
143 
getOutputStream()144     public OutputStream getOutputStream() throws IOException {
145         if (sslSocket != null) {
146             if (sslOutputStream == null) {
147                 sslOutputStream = sslSocket.getOutputStream();
148             }
149             return sslOutputStream;
150         } else if(outputStream == null) {
151             outputStream = socket.getOutputStream();
152         }
153         return outputStream;
154     }
155 
getInputStream()156     public InputStream getInputStream() throws IOException {
157         if (sslSocket != null) {
158             if (sslInputStream == null) {
159                 sslInputStream = sslSocket.getInputStream();
160             }
161             return sslInputStream;
162         } else if (inputStream == null) {
163             /*
164              * Buffer the socket stream to permit efficient parsing of HTTP
165              * headers and chunk sizes. Benchmarks suggest 128 is sufficient.
166              * We cannot buffer when setting up a tunnel because we may consume
167              * bytes intended for the SSL socket.
168              */
169             int bufferSize = 128;
170             inputStream = address.requiresTunnel
171                     ? socket.getInputStream()
172                     : new BufferedInputStream(socket.getInputStream(), bufferSize);
173         }
174         return inputStream;
175     }
176 
getSocket()177     protected Socket getSocket() {
178         return sslSocket != null ? sslSocket : socket;
179     }
180 
getAddress()181     public Address getAddress() {
182         return address;
183     }
184 
185     /**
186      * Create an {@code SSLSocket} and perform the SSL handshake
187      * (performing certificate validation.
188      *
189      * @param sslSocketFactory Source of new {@code SSLSocket} instances.
190      * @param tlsTolerant If true, assume server can handle common
191      * TLS extensions and SSL deflate compression. If false, use
192      * an SSL3 only fallback mode without compression.
193      */
setupSecureSocket(SSLSocketFactory sslSocketFactory, boolean tlsTolerant)194     public void setupSecureSocket(SSLSocketFactory sslSocketFactory, boolean tlsTolerant)
195             throws IOException {
196         // create the wrapper over connected socket
197         unverifiedSocket = (SSLSocket) sslSocketFactory.createSocket(socket,
198                 address.uriHost, address.uriPort, true /* autoClose */);
199         // tlsTolerant mimics Chrome's behavior
200         if (tlsTolerant && unverifiedSocket instanceof OpenSSLSocketImpl) {
201             OpenSSLSocketImpl openSslSocket = (OpenSSLSocketImpl) unverifiedSocket;
202             openSslSocket.setEnabledCompressionMethods(new String[] { "ZLIB"});
203             openSslSocket.setUseSessionTickets(true);
204             openSslSocket.setHostname(address.socketHost);
205             // use SSLSocketFactory default enabled protocols
206         } else {
207             unverifiedSocket.setEnabledProtocols(new String [] { "SSLv3" });
208         }
209         // force handshake, which can throw
210         unverifiedSocket.startHandshake();
211     }
212 
213     /**
214      * Return an {@code SSLSocket} that is not only connected but has
215      * also passed hostname verification.
216      *
217      * @param hostnameVerifier Used to verify the hostname we
218      * connected to is an acceptable match for the peer certificate
219      * chain of the SSLSession.
220      */
verifySecureSocketHostname(HostnameVerifier hostnameVerifier)221     public SSLSocket verifySecureSocketHostname(HostnameVerifier hostnameVerifier)
222             throws IOException {
223         if (!hostnameVerifier.verify(address.uriHost, unverifiedSocket.getSession())) {
224             throw new IOException("Hostname '" + address.uriHost + "' was not verified");
225         }
226         sslSocket = unverifiedSocket;
227         return sslSocket;
228     }
229 
230     /**
231      * Return an {@code SSLSocket} if already connected, otherwise null.
232      */
getSecureSocketIfConnected()233     public SSLSocket getSecureSocketIfConnected() {
234         return sslSocket;
235     }
236 
237     /**
238      * Returns true if this connection has been used to satisfy an earlier
239      * HTTP request/response pair.
240      */
isRecycled()241     public boolean isRecycled() {
242         return recycled;
243     }
244 
setRecycled()245     public void setRecycled() {
246         this.recycled = true;
247     }
248 
249     /**
250      * Returns true if this connection is eligible to be reused for another
251      * request/response pair.
252      */
isEligibleForRecycling()253     protected boolean isEligibleForRecycling() {
254         return !socket.isClosed()
255                 && !socket.isInputShutdown()
256                 && !socket.isOutputShutdown();
257     }
258 
259     /**
260      * This address has two parts: the address we connect to directly and the
261      * origin address of the resource. These are the same unless a proxy is
262      * being used. It also includes the SSL socket factory so that a socket will
263      * not be reused if its SSL configuration is different.
264      */
265     public static final class Address {
266         private final Proxy proxy;
267         private final boolean requiresTunnel;
268         private final String uriHost;
269         private final int uriPort;
270         private final String socketHost;
271         private final int socketPort;
272         private final SSLSocketFactory sslSocketFactory;
273 
Address(URI uri, SSLSocketFactory sslSocketFactory)274         public Address(URI uri, SSLSocketFactory sslSocketFactory) throws UnknownHostException {
275             this.proxy = null;
276             this.requiresTunnel = false;
277             this.uriHost = uri.getHost();
278             this.uriPort = uri.getEffectivePort();
279             this.sslSocketFactory = sslSocketFactory;
280             this.socketHost = uriHost;
281             this.socketPort = uriPort;
282             if (uriHost == null) {
283                 throw new UnknownHostException(uri.toString());
284             }
285         }
286 
287         /**
288          * @param requiresTunnel true if the HTTP connection needs to tunnel one
289          *     protocol over another, such as when using HTTPS through an HTTP
290          *     proxy. When doing so, we must avoid buffering bytes intended for
291          *     the higher-level protocol.
292          */
Address(URI uri, SSLSocketFactory sslSocketFactory, Proxy proxy, boolean requiresTunnel)293         public Address(URI uri, SSLSocketFactory sslSocketFactory,
294                 Proxy proxy, boolean requiresTunnel) throws UnknownHostException {
295             this.proxy = proxy;
296             this.requiresTunnel = requiresTunnel;
297             this.uriHost = uri.getHost();
298             this.uriPort = uri.getEffectivePort();
299             this.sslSocketFactory = sslSocketFactory;
300 
301             SocketAddress proxyAddress = proxy.address();
302             if (!(proxyAddress instanceof InetSocketAddress)) {
303                 throw new IllegalArgumentException("Proxy.address() is not an InetSocketAddress: "
304                         + proxyAddress.getClass());
305             }
306             InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
307             this.socketHost = proxySocketAddress.getHostName();
308             this.socketPort = proxySocketAddress.getPort();
309             if (uriHost == null) {
310                 throw new UnknownHostException(uri.toString());
311             }
312         }
313 
getProxy()314         public Proxy getProxy() {
315             return proxy;
316         }
317 
equals(Object other)318         @Override public boolean equals(Object other) {
319             if (other instanceof Address) {
320                 Address that = (Address) other;
321                 return Objects.equal(this.proxy, that.proxy)
322                         && this.uriHost.equals(that.uriHost)
323                         && this.uriPort == that.uriPort
324                         && Objects.equal(this.sslSocketFactory, that.sslSocketFactory)
325                         && this.requiresTunnel == that.requiresTunnel;
326             }
327             return false;
328         }
329 
hashCode()330         @Override public int hashCode() {
331             int result = 17;
332             result = 31 * result + uriHost.hashCode();
333             result = 31 * result + uriPort;
334             result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
335             result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
336             result = 31 * result + (requiresTunnel ? 1 : 0);
337             return result;
338         }
339 
connect(int connectTimeout)340         public HttpConnection connect(int connectTimeout) throws IOException {
341             return new HttpConnection(this, connectTimeout);
342         }
343     }
344 }
345