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 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