PageRenderTime 53ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/luni/src/main/java/libcore/net/http/HttpConnection.java

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