Home | History | Annotate | Download | only in http
      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 dalvik.system.SocketTagger;
     21 import java.io.IOException;
     22 import java.net.Socket;
     23 import java.net.SocketException;
     24 import java.util.ArrayList;
     25 import java.util.HashMap;
     26 import java.util.List;
     27 
     28 /**
     29  * A pool of HTTP connections. This class exposes its tuning parameters as
     30  * system properties:
     31  * <ul>
     32  *   <li>{@code http.keepAlive} true if HTTP connections should be pooled at
     33  *       all. Default is true.
     34  *   <li>{@code http.maxConnections} maximum number of connections to each URI.
     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 
     44     public static final HttpConnectionPool INSTANCE = new HttpConnectionPool();
     45 
     46     private final int maxConnections;
     47     private final HashMap<HttpConnection.Address, List<HttpConnection>> connectionPool
     48             = new HashMap<HttpConnection.Address, List<HttpConnection>>();
     49 
     50     private HttpConnectionPool() {
     51         String keepAlive = System.getProperty("http.keepAlive");
     52         if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {
     53             maxConnections = 0;
     54             return;
     55         }
     56 
     57         String maxConnectionsString = System.getProperty("http.maxConnections");
     58         this.maxConnections = maxConnectionsString != null
     59                 ? Integer.parseInt(maxConnectionsString)
     60                 : 5;
     61     }
     62 
     63     public HttpConnection get(HttpConnection.Address address, int connectTimeout)
     64             throws IOException {
     65         // First try to reuse an existing HTTP connection.
     66         synchronized (connectionPool) {
     67             List<HttpConnection> connections = connectionPool.get(address);
     68             while (connections != null) {
     69                 HttpConnection connection = connections.remove(connections.size() - 1);
     70                 if (connections.isEmpty()) {
     71                     connectionPool.remove(address);
     72                     connections = null;
     73                 }
     74                 if (connection.isEligibleForRecycling()) {
     75                     // Since Socket is recycled, re-tag before using
     76                     Socket socket = connection.getSocket();
     77                     SocketTagger.get().tag(socket);
     78                     return connection;
     79                 }
     80             }
     81         }
     82 
     83         /*
     84          * We couldn't find a reusable connection, so we need to create a new
     85          * connection. We're careful not to do so while holding a lock!
     86          */
     87         return address.connect(connectTimeout);
     88     }
     89 
     90     public void recycle(HttpConnection connection) {
     91         Socket socket = connection.getSocket();
     92         try {
     93             SocketTagger.get().untag(socket);
     94         } catch (SocketException e) {
     95             // When unable to remove tagging, skip recycling and close
     96             System.logW("Unable to untagSocket(): " + e);
     97             connection.closeSocketAndStreams();
     98             return;
     99         }
    100 
    101         if (maxConnections > 0 && connection.isEligibleForRecycling()) {
    102             HttpConnection.Address address = connection.getAddress();
    103             synchronized (connectionPool) {
    104                 List<HttpConnection> connections = connectionPool.get(address);
    105                 if (connections == null) {
    106                     connections = new ArrayList<HttpConnection>();
    107                     connectionPool.put(address, connections);
    108                 }
    109                 if (connections.size() < maxConnections) {
    110                     connection.setRecycled();
    111                     connections.add(connection);
    112                     return; // keep the connection open
    113                 }
    114             }
    115         }
    116 
    117         // don't close streams while holding a lock!
    118         connection.closeSocketAndStreams();
    119     }
    120 }
    121