View Javadoc

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 }