    1   /***
    2    * ====================================================================
    3    *
    4    *  Licensed under the Apache License, Version 2.0 (the "License");
    5    *  you may not use this file except in compliance with the License.
    6    *  You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   *  Unless required by applicable law or agreed to in writing, software
    11   *  distributed under the License is distributed on an "AS IS" BASIS,
    12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   *  See the License for the specific language governing permissions and
    14   *  limitations under the License.
    15   * ====================================================================
    16   *
    17   */
    18  package org.archive.httpclient;
    19
    20  import java.io.IOException;
    21  import java.io.InputStream;
    22  import java.util.ArrayList;
    23  import java.util.Iterator;
    24  import java.util.List;
    25  import java.util.logging.Level;
    26  import java.util.logging.Logger;
    27
    28  import org.apache.commons.httpclient.HostConfiguration;
    29  import org.apache.commons.httpclient.HttpConnection;
    30  import org.apache.commons.httpclient.HttpConnectionManager;
    31  import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
    32
    33  /***
    34   * A simple, but thread-safe HttpClient {@link HttpConnectionManager}.
    35   * Based on {@link org.apache.commons.httpclient.SimpleHttpConnectionManager}.
    36   *
    37   * <b>Java &gt;= 1.4 is recommended.</b>
    38   *
    39   * @author Christian Kohlschuetter
    40   */
    41  public final class ThreadLocalHttpConnectionManager implements
    42      HttpConnectionManager {
    43
    44      private static final CloserThread closer = new CloserThread();
    45      private static final Logger logger = Logger
    46          .getLogger(ThreadLocalHttpConnectionManager.class.getName());
    47
    48      private final ThreadLocal tl = new ThreadLocal() {
    49          protected synchronized Object initialValue() {
    50              return new ConnectionInfo();
    51          }
    52      };
    53
    54      private ConnectionInfo getConnectionInfo() {
    55          return (ConnectionInfo) tl.get();
    56      }
    57
    58      private final class ConnectionInfo {
    59          /*** The http connection */
    60          private HttpConnection conn = null;
    61
    62          /***
    63           * The time the connection was made idle.
    64           */
    65          private long idleStartTime = Long.MAX_VALUE;
    66      }
    67
    68      public ThreadLocalHttpConnectionManager() {
    69      }
    70
    71      /***
    72       * Since the same connection is about to be reused, make sure the
    73       * previous request was completely processed, and if not
    74       * consume it now.
    75       * @param conn The connection
    76       * @return true, if the connection is reusable
    77       */
    78      private static boolean finishLastResponse(final HttpConnection conn) {
    79          InputStream lastResponse = conn.getLastResponseInputStream();
    80          if(lastResponse != null) {
    81              conn.setLastResponseInputStream(null);
    82              try {
    83                  lastResponse.close();
    84                  return true;
    85              } catch (IOException ioe) {
    86                  // force reconnect.
    87                  return false;
    88              }
    89          } else {
    90              return false;
    91          }
    92      }
    93
    94      /***
    95       * Collection of parameters associated with this connection manager.
    96       */
    97      private HttpConnectionManagerParams params = new HttpConnectionManagerParams();
    98
    99      /***
    100      * @see HttpConnectionManager#getConnection(HostConfiguration)
    101      */
    102     public HttpConnection getConnection(
    103         final HostConfiguration hostConfiguration) {
    104         return getConnection(hostConfiguration, 0);
    105     }
    106
    107     /***
    108      * Gets the staleCheckingEnabled value to be set on HttpConnections that are created.
    109      *
    110      * @return <code>true</code> if stale checking will be enabled on HttpConections
    111      *
    112      * @see HttpConnection#isStaleCheckingEnabled()
    113      *
    114      * @deprecated Use {@link HttpConnectionManagerParams#isStaleCheckingEnabled()},
    115      * {@link HttpConnectionManager#getParams()}.
    116      */
    117     public boolean isConnectionStaleCheckingEnabled() {
    118         return this.params.isStaleCheckingEnabled();
    119     }
    120
    121     /***
    122      * Sets the staleCheckingEnabled value to be set on HttpConnections that are created.
    123      *
    124      * @param connectionStaleCheckingEnabled <code>true</code> if stale checking will be enabled
    125      * on HttpConections
    126      *
    127      * @see HttpConnection#setStaleCheckingEnabled(boolean)
    128      *
    129      * @deprecated Use {@link HttpConnectionManagerParams#setStaleCheckingEnabled(boolean)},
    130      * {@link HttpConnectionManager#getParams()}.
    131      */
    132     public void setConnectionStaleCheckingEnabled(
    133         final boolean connectionStaleCheckingEnabled) {
    134         this.params.setStaleCheckingEnabled(connectionStaleCheckingEnabled);
    135     }
    136
    137     /***
    138      * @see HttpConnectionManager#getConnectionWithTimeout(HostConfiguration, long)
    139      *
    140      * @since 3.0
    141      */
    142     public HttpConnection getConnectionWithTimeout(
    143         final HostConfiguration hostConfiguration, final long timeout) {
    144
    145         final ConnectionInfo ci = getConnectionInfo();
    146         HttpConnection httpConnection = ci.conn;
    147
    148         // make sure the host and proxy are correct for this connection
    149         // close it and set the values if they are not
    150         if(httpConnection == null || !finishLastResponse(httpConnection)
    151             || !hostConfiguration.hostEquals(httpConnection)
    152             || !hostConfiguration.proxyEquals(httpConnection)) {
    153
    154             if(httpConnection != null && httpConnection.isOpen()) {
    155                 closer.closeConnection(httpConnection);
    156             }
    157
    158             httpConnection = new HttpConnection(hostConfiguration);
    159             httpConnection.setHttpConnectionManager(this);
    160             httpConnection.getParams().setDefaults(this.params);
    161             ci.conn = httpConnection;
    162
    163             httpConnection.setHost(hostConfiguration.getHost());
    164             httpConnection.setPort(hostConfiguration.getPort());
    165             httpConnection.setProtocol(hostConfiguration.getProtocol());
    166             httpConnection.setLocalAddress(hostConfiguration.getLocalAddress());
    167
    168             httpConnection.setProxyHost(hostConfiguration.getProxyHost());
    169             httpConnection.setProxyPort(hostConfiguration.getProxyPort());
    170         }
    171
    172         // remove the connection from the timeout handler
    173         ci.idleStartTime = Long.MAX_VALUE;
    174
    175         return httpConnection;
    176     }
    177
    178     /***
    179      * @see HttpConnectionManager#getConnection(HostConfiguration, long)
    180      *
    181      * @deprecated Use #getConnectionWithTimeout(HostConfiguration, long)
    182      */
    183     public HttpConnection getConnection(
    184         final HostConfiguration hostConfiguration, final long timeout) {
    185         return getConnectionWithTimeout(hostConfiguration, timeout);
    186     }
    187
    188     /***
    189      * @see HttpConnectionManager#releaseConnection(org.apache.commons.httpclient.HttpConnection)
    190      */
    191     public void releaseConnection(final HttpConnection conn) {
    192         final ConnectionInfo ci = getConnectionInfo();
    193         HttpConnection httpConnection = ci.conn;
    194
    195         if(conn != httpConnection) {
    196             throw new IllegalStateException(
    197                 "Unexpected release of an unknown connection.");
    198         }
    199
    200         finishLastResponse(httpConnection);
    201
    202         // track the time the connection was made idle
    203         ci.idleStartTime = System.currentTimeMillis();
    204     }
    205
    206     /***
    207      * Returns {@link HttpConnectionManagerParams parameters} associated
    208      * with this connection manager.
    209      *
    210      * @since 2.1
    211      *
    212      * @see HttpConnectionManagerParams
    213      */
    214     public HttpConnectionManagerParams getParams() {
    215         return this.params;
    216     }
    217
    218     /***
    219      * Assigns {@link HttpConnectionManagerParams parameters} for this
    220      * connection manager.
    221      *
    222      * @since 2.1
    223      *
    224      * @see HttpConnectionManagerParams
    225      */
    226     public void setParams(final HttpConnectionManagerParams p) {
    227         if(p == null) {
    228             throw new IllegalArgumentException("Parameters may not be null");
    229         }
    230         this.params = p;
    231     }
    232
    233     /***
    234      * @since 3.0
    235      */
    236     public void closeIdleConnections(final long idleTimeout) {
    237         long maxIdleTime = System.currentTimeMillis() - idleTimeout;
    238
    239         final ConnectionInfo ci = getConnectionInfo();
    240
    241         if(ci.idleStartTime <= maxIdleTime) {
    242             ci.conn.close();
    243         }
    244     }
    245
    246     private static final class CloserThread extends Thread {
    247         private List<HttpConnection> connections
    248          = new ArrayList<HttpConnection>();
    249
    250         private static final int SLEEP_INTERVAL = 5000;
    251
    252         public CloserThread() {
    253             super("HttpConnection closer");
    254             // Make this a daemon thread so it can't be responsible for the JVM
    255             // not shutting down.
    256             setDaemon(true);
    257             start();
    258         }
    259
    260         public void closeConnection(final HttpConnection conn) {
    261             synchronized (connections) {
    262                 connections.add(conn);
    263             }
    264         }
    265
    266         public void run() {
    267             try {
    268                 while (!Thread.interrupted()) {
    269                     Thread.sleep(SLEEP_INTERVAL);
    270
    271                     List<HttpConnection> s;
    272                     synchronized (connections) {
    273                         s = connections;
    274                         connections = new ArrayList<HttpConnection>();
    275                     }
    276                     logger.log(Level.INFO, "Closing " + s.size()
    277                         + " HttpConnections");
    278                     for(final Iterator<HttpConnection> it = s.iterator();
    279                       it.hasNext();) {
    280                         HttpConnection conn = it.next();
    281                         conn.close();
    282                         conn.setHttpConnectionManager(null);
    283                         it.remove();
    284                     }
    285                 }
    286             } catch (InterruptedException e) {
    287                 return;
    288             }
    289         }
    290     }
    291 }