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.IOException; 21 import java.net.Socket; 22 import java.net.SocketException; 23 import java.util.ArrayList; 24 import java.util.HashMap; 25 import java.util.List; 26 import libcore.util.Libcore; 27 28 /** 29 * A pool of HTTP and SPDY connections. This class exposes its tuning parameters 30 * as system properties: 31 * <ul> 32 * <li>{@code http.keepAlive} true if HTTP and SPDY connections should be 33 * pooled at all. Default is true. 34 * <li>{@code http.maxConnections} maximum number of connections to each host. 35 * Default is 5. 36 * </ul> 37 * 38 * <p>This class <i>doesn't</i> adjust its configuration as system properties 39 * are changed. This assumes that the applications that set these parameters do 40 * so before making HTTP connections, and that this class is initialized lazily. 41 */ 42 final class HttpConnectionPool { 43 public static final HttpConnectionPool INSTANCE = new HttpConnectionPool(); 44 45 private final int maxConnections; 46 private final HashMap<HttpConnection.Address, List<HttpConnection>> connectionPool 47 = new HashMap<HttpConnection.Address, List<HttpConnection>>(); 48 49 private HttpConnectionPool() { 50 String keepAlive = System.getProperty("http.keepAlive"); 51 if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) { 52 maxConnections = 0; 53 return; 54 } 55 56 String maxConnectionsString = System.getProperty("http.maxConnections"); 57 this.maxConnections = maxConnectionsString != null 58 ? Integer.parseInt(maxConnectionsString) 59 : 5; 60 } 61 62 public HttpConnection get(HttpConnection.Address address, int connectTimeout) 63 throws IOException { 64 // First try to reuse an existing HTTP connection. 65 synchronized (connectionPool) { 66 List<HttpConnection> connections = connectionPool.get(address); 67 while (connections != null) { 68 HttpConnection connection = connections.get(connections.size() - 1); 69 if (!connection.isSpdy()) { 70 connections.remove(connections.size() - 1); 71 } 72 if (connections.isEmpty()) { 73 connectionPool.remove(address); 74 connections = null; 75 } 76 if (connection.isEligibleForRecycling()) { 77 // Since Socket is recycled, re-tag before using 78 Socket socket = connection.getSocket(); 79 Libcore.tagSocket(socket); 80 return connection; 81 } 82 } 83 } 84 85 /* 86 * We couldn't find a reusable connection, so we need to create a new 87 * connection. We're careful not to do so while holding a lock! 88 */ 89 return address.connect(connectTimeout); 90 } 91 92 /** 93 * Gives the HTTP/HTTPS connection to the pool. It is an error to use {@code 94 * connection} after calling this method. 95 */ 96 public void recycle(HttpConnection connection) { 97 if (connection.isSpdy()) { 98 throw new IllegalArgumentException(); 99 } 100 101 Socket socket = connection.getSocket(); 102 try { 103 Libcore.untagSocket(socket); 104 } catch (SocketException e) { 105 // When unable to remove tagging, skip recycling and close 106 Libcore.logW("Unable to untagSocket(): " + e); 107 connection.closeSocketAndStreams(); 108 return; 109 } 110 111 if (maxConnections > 0 && connection.isEligibleForRecycling()) { 112 HttpConnection.Address address = connection.getAddress(); 113 synchronized (connectionPool) { 114 List<HttpConnection> connections = connectionPool.get(address); 115 if (connections == null) { 116 connections = new ArrayList<HttpConnection>(); 117 connectionPool.put(address, connections); 118 } 119 if (connections.size() < maxConnections) { 120 connection.setRecycled(); 121 connections.add(connection); 122 return; // keep the connection open 123 } 124 } 125 } 126 127 // don't close streams while holding a lock! 128 connection.closeSocketAndStreams(); 129 } 130 131 /** 132 * Shares the SPDY connection with the pool. Callers to this method may 133 * continue to use {@code connection}. 134 */ 135 public void share(HttpConnection connection) { 136 if (!connection.isSpdy()) { 137 throw new IllegalArgumentException(); 138 } 139 if (maxConnections <= 0 || !connection.isEligibleForRecycling()) { 140 return; 141 } 142 HttpConnection.Address address = connection.getAddress(); 143 synchronized (connectionPool) { 144 List<HttpConnection> connections = connectionPool.get(address); 145 if (connections == null) { 146 connections = new ArrayList<HttpConnection>(1); 147 connections.add(connection); 148 connectionPool.put(address, connections); 149 } 150 } 151 } 152 } 153