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 >= 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
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
149
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
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
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
255
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 }