|
1 /* |
|
2 * ==================================================================== |
|
3 * Licensed to the Apache Software Foundation (ASF) under one |
|
4 * or more contributor license agreements. See the NOTICE file |
|
5 * distributed with this work for additional information |
|
6 * regarding copyright ownership. The ASF licenses this file |
|
7 * to you under the Apache License, Version 2.0 (the |
|
8 * "License"); you may not use this file except in compliance |
|
9 * with the License. You may obtain a copy of the License at |
|
10 * |
|
11 * http://www.apache.org/licenses/LICENSE-2.0 |
|
12 * |
|
13 * Unless required by applicable law or agreed to in writing, |
|
14 * software distributed under the License is distributed on an |
|
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
|
16 * KIND, either express or implied. See the License for the |
|
17 * specific language governing permissions and limitations |
|
18 * under the License. |
|
19 * ==================================================================== |
|
20 * |
|
21 * This software consists of voluntary contributions made by many |
|
22 * individuals on behalf of the Apache Software Foundation. For more |
|
23 * information on the Apache Software Foundation, please see |
|
24 * <http://www.apache.org/>. |
|
25 * |
|
26 */ |
|
27 |
|
28 package ch.boye.httpclientandroidlib.impl.conn; |
|
29 |
|
30 import java.io.IOException; |
|
31 import java.util.concurrent.TimeUnit; |
|
32 |
|
33 import ch.boye.httpclientandroidlib.annotation.GuardedBy; |
|
34 import ch.boye.httpclientandroidlib.annotation.ThreadSafe; |
|
35 |
|
36 import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; |
|
37 /* LogFactory removed by HttpClient for Android script. */ |
|
38 import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; |
|
39 import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; |
|
40 import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; |
|
41 import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; |
|
42 import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; |
|
43 import ch.boye.httpclientandroidlib.conn.routing.RouteTracker; |
|
44 import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; |
|
45 import ch.boye.httpclientandroidlib.params.HttpParams; |
|
46 |
|
47 /** |
|
48 * A connection manager for a single connection. This connection manager |
|
49 * maintains only one active connection at a time. Even though this class |
|
50 * is thread-safe it ought to be used by one execution thread only. |
|
51 * <p> |
|
52 * SingleClientConnManager will make an effort to reuse the connection |
|
53 * for subsequent requests with the same {@link HttpRoute route}. |
|
54 * It will, however, close the existing connection and open it |
|
55 * for the given route, if the route of the persistent connection does |
|
56 * not match that of the connection request. If the connection has been |
|
57 * already been allocated {@link IllegalStateException} is thrown. |
|
58 * |
|
59 * @since 4.0 |
|
60 */ |
|
61 @ThreadSafe |
|
62 public class SingleClientConnManager implements ClientConnectionManager { |
|
63 |
|
64 public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); |
|
65 |
|
66 /** The message to be logged on multiple allocation. */ |
|
67 public final static String MISUSE_MESSAGE = |
|
68 "Invalid use of SingleClientConnManager: connection still allocated.\n" + |
|
69 "Make sure to release the connection before allocating another one."; |
|
70 |
|
71 /** The schemes supported by this connection manager. */ |
|
72 protected final SchemeRegistry schemeRegistry; |
|
73 |
|
74 /** The operator for opening and updating connections. */ |
|
75 protected final ClientConnectionOperator connOperator; |
|
76 |
|
77 /** Whether the connection should be shut down on release. */ |
|
78 protected final boolean alwaysShutDown; |
|
79 |
|
80 /** The one and only entry in this pool. */ |
|
81 @GuardedBy("this") |
|
82 protected PoolEntry uniquePoolEntry; |
|
83 |
|
84 /** The currently issued managed connection, if any. */ |
|
85 @GuardedBy("this") |
|
86 protected ConnAdapter managedConn; |
|
87 |
|
88 /** The time of the last connection release, or -1. */ |
|
89 @GuardedBy("this") |
|
90 protected long lastReleaseTime; |
|
91 |
|
92 /** The time the last released connection expires and shouldn't be reused. */ |
|
93 @GuardedBy("this") |
|
94 protected long connectionExpiresTime; |
|
95 |
|
96 /** Indicates whether this connection manager is shut down. */ |
|
97 protected volatile boolean isShutDown; |
|
98 |
|
99 /** |
|
100 * Creates a new simple connection manager. |
|
101 * |
|
102 * @param params the parameters for this manager |
|
103 * @param schreg the scheme registry |
|
104 * |
|
105 * @deprecated use {@link SingleClientConnManager#SingleClientConnManager(SchemeRegistry)} |
|
106 */ |
|
107 @Deprecated |
|
108 public SingleClientConnManager(HttpParams params, |
|
109 SchemeRegistry schreg) { |
|
110 this(schreg); |
|
111 } |
|
112 /** |
|
113 * Creates a new simple connection manager. |
|
114 * |
|
115 * @param schreg the scheme registry |
|
116 */ |
|
117 public SingleClientConnManager(final SchemeRegistry schreg) { |
|
118 if (schreg == null) { |
|
119 throw new IllegalArgumentException |
|
120 ("Scheme registry must not be null."); |
|
121 } |
|
122 this.schemeRegistry = schreg; |
|
123 this.connOperator = createConnectionOperator(schreg); |
|
124 this.uniquePoolEntry = new PoolEntry(); |
|
125 this.managedConn = null; |
|
126 this.lastReleaseTime = -1L; |
|
127 this.alwaysShutDown = false; //@@@ from params? as argument? |
|
128 this.isShutDown = false; |
|
129 } |
|
130 |
|
131 /** |
|
132 * @since 4.1 |
|
133 */ |
|
134 public SingleClientConnManager() { |
|
135 this(SchemeRegistryFactory.createDefault()); |
|
136 } |
|
137 |
|
138 @Override |
|
139 protected void finalize() throws Throwable { |
|
140 try { |
|
141 shutdown(); |
|
142 } finally { // Make sure we call overridden method even if shutdown barfs |
|
143 super.finalize(); |
|
144 } |
|
145 } |
|
146 |
|
147 public SchemeRegistry getSchemeRegistry() { |
|
148 return this.schemeRegistry; |
|
149 } |
|
150 |
|
151 /** |
|
152 * Hook for creating the connection operator. |
|
153 * It is called by the constructor. |
|
154 * Derived classes can override this method to change the |
|
155 * instantiation of the operator. |
|
156 * The default implementation here instantiates |
|
157 * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. |
|
158 * |
|
159 * @param schreg the scheme registry to use, or <code>null</code> |
|
160 * |
|
161 * @return the connection operator to use |
|
162 */ |
|
163 protected ClientConnectionOperator |
|
164 createConnectionOperator(SchemeRegistry schreg) { |
|
165 return new DefaultClientConnectionOperator(schreg); |
|
166 } |
|
167 |
|
168 /** |
|
169 * Asserts that this manager is not shut down. |
|
170 * |
|
171 * @throws IllegalStateException if this manager is shut down |
|
172 */ |
|
173 protected final void assertStillUp() throws IllegalStateException { |
|
174 if (this.isShutDown) |
|
175 throw new IllegalStateException("Manager is shut down."); |
|
176 } |
|
177 |
|
178 public final ClientConnectionRequest requestConnection( |
|
179 final HttpRoute route, |
|
180 final Object state) { |
|
181 |
|
182 return new ClientConnectionRequest() { |
|
183 |
|
184 public void abortRequest() { |
|
185 // Nothing to abort, since requests are immediate. |
|
186 } |
|
187 |
|
188 public ManagedClientConnection getConnection( |
|
189 long timeout, TimeUnit tunit) { |
|
190 return SingleClientConnManager.this.getConnection( |
|
191 route, state); |
|
192 } |
|
193 |
|
194 }; |
|
195 } |
|
196 |
|
197 /** |
|
198 * Obtains a connection. |
|
199 * |
|
200 * @param route where the connection should point to |
|
201 * |
|
202 * @return a connection that can be used to communicate |
|
203 * along the given route |
|
204 */ |
|
205 public synchronized ManagedClientConnection getConnection(HttpRoute route, Object state) { |
|
206 if (route == null) { |
|
207 throw new IllegalArgumentException("Route may not be null."); |
|
208 } |
|
209 assertStillUp(); |
|
210 |
|
211 if (log.isDebugEnabled()) { |
|
212 log.debug("Get connection for route " + route); |
|
213 } |
|
214 |
|
215 if (managedConn != null) |
|
216 throw new IllegalStateException(MISUSE_MESSAGE); |
|
217 |
|
218 // check re-usability of the connection |
|
219 boolean recreate = false; |
|
220 boolean shutdown = false; |
|
221 |
|
222 // Kill the connection if it expired. |
|
223 closeExpiredConnections(); |
|
224 |
|
225 if (uniquePoolEntry.connection.isOpen()) { |
|
226 RouteTracker tracker = uniquePoolEntry.tracker; |
|
227 shutdown = (tracker == null || // can happen if method is aborted |
|
228 !tracker.toRoute().equals(route)); |
|
229 } else { |
|
230 // If the connection is not open, create a new PoolEntry, |
|
231 // as the connection may have been marked not reusable, |
|
232 // due to aborts -- and the PoolEntry should not be reused |
|
233 // either. There's no harm in recreating an entry if |
|
234 // the connection is closed. |
|
235 recreate = true; |
|
236 } |
|
237 |
|
238 if (shutdown) { |
|
239 recreate = true; |
|
240 try { |
|
241 uniquePoolEntry.shutdown(); |
|
242 } catch (IOException iox) { |
|
243 log.debug("Problem shutting down connection.", iox); |
|
244 } |
|
245 } |
|
246 |
|
247 if (recreate) |
|
248 uniquePoolEntry = new PoolEntry(); |
|
249 |
|
250 managedConn = new ConnAdapter(uniquePoolEntry, route); |
|
251 |
|
252 return managedConn; |
|
253 } |
|
254 |
|
255 public synchronized void releaseConnection( |
|
256 ManagedClientConnection conn, |
|
257 long validDuration, TimeUnit timeUnit) { |
|
258 assertStillUp(); |
|
259 |
|
260 if (!(conn instanceof ConnAdapter)) { |
|
261 throw new IllegalArgumentException |
|
262 ("Connection class mismatch, " + |
|
263 "connection not obtained from this manager."); |
|
264 } |
|
265 |
|
266 if (log.isDebugEnabled()) { |
|
267 log.debug("Releasing connection " + conn); |
|
268 } |
|
269 |
|
270 ConnAdapter sca = (ConnAdapter) conn; |
|
271 if (sca.poolEntry == null) |
|
272 return; // already released |
|
273 ClientConnectionManager manager = sca.getManager(); |
|
274 if (manager != null && manager != this) { |
|
275 throw new IllegalArgumentException |
|
276 ("Connection not obtained from this manager."); |
|
277 } |
|
278 |
|
279 try { |
|
280 // make sure that the response has been read completely |
|
281 if (sca.isOpen() && (this.alwaysShutDown || |
|
282 !sca.isMarkedReusable()) |
|
283 ) { |
|
284 if (log.isDebugEnabled()) { |
|
285 log.debug |
|
286 ("Released connection open but not reusable."); |
|
287 } |
|
288 |
|
289 // make sure this connection will not be re-used |
|
290 // we might have gotten here because of a shutdown trigger |
|
291 // shutdown of the adapter also clears the tracked route |
|
292 sca.shutdown(); |
|
293 } |
|
294 } catch (IOException iox) { |
|
295 if (log.isDebugEnabled()) |
|
296 log.debug("Exception shutting down released connection.", |
|
297 iox); |
|
298 } finally { |
|
299 sca.detach(); |
|
300 managedConn = null; |
|
301 lastReleaseTime = System.currentTimeMillis(); |
|
302 if(validDuration > 0) |
|
303 connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime; |
|
304 else |
|
305 connectionExpiresTime = Long.MAX_VALUE; |
|
306 } |
|
307 } |
|
308 |
|
309 public synchronized void closeExpiredConnections() { |
|
310 if(System.currentTimeMillis() >= connectionExpiresTime) { |
|
311 closeIdleConnections(0, TimeUnit.MILLISECONDS); |
|
312 } |
|
313 } |
|
314 |
|
315 public synchronized void closeIdleConnections(long idletime, TimeUnit tunit) { |
|
316 assertStillUp(); |
|
317 |
|
318 // idletime can be 0 or negative, no problem there |
|
319 if (tunit == null) { |
|
320 throw new IllegalArgumentException("Time unit must not be null."); |
|
321 } |
|
322 |
|
323 if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) { |
|
324 final long cutoff = |
|
325 System.currentTimeMillis() - tunit.toMillis(idletime); |
|
326 if (lastReleaseTime <= cutoff) { |
|
327 try { |
|
328 uniquePoolEntry.close(); |
|
329 } catch (IOException iox) { |
|
330 // ignore |
|
331 log.debug("Problem closing idle connection.", iox); |
|
332 } |
|
333 } |
|
334 } |
|
335 } |
|
336 |
|
337 public synchronized void shutdown() { |
|
338 |
|
339 this.isShutDown = true; |
|
340 |
|
341 if (managedConn != null) |
|
342 managedConn.detach(); |
|
343 |
|
344 try { |
|
345 if (uniquePoolEntry != null) // and connection open? |
|
346 uniquePoolEntry.shutdown(); |
|
347 } catch (IOException iox) { |
|
348 // ignore |
|
349 log.debug("Problem while shutting down manager.", iox); |
|
350 } finally { |
|
351 uniquePoolEntry = null; |
|
352 } |
|
353 } |
|
354 |
|
355 /** |
|
356 * @deprecated no longer used |
|
357 */ |
|
358 @Deprecated |
|
359 protected synchronized void revokeConnection() { |
|
360 if (managedConn == null) |
|
361 return; |
|
362 managedConn.detach(); |
|
363 try { |
|
364 uniquePoolEntry.shutdown(); |
|
365 } catch (IOException iox) { |
|
366 // ignore |
|
367 log.debug("Problem while shutting down connection.", iox); |
|
368 } |
|
369 } |
|
370 |
|
371 /** |
|
372 * The pool entry for this connection manager. |
|
373 */ |
|
374 protected class PoolEntry extends AbstractPoolEntry { |
|
375 |
|
376 /** |
|
377 * Creates a new pool entry. |
|
378 * |
|
379 */ |
|
380 protected PoolEntry() { |
|
381 super(SingleClientConnManager.this.connOperator, null); |
|
382 } |
|
383 |
|
384 /** |
|
385 * Closes the connection in this pool entry. |
|
386 */ |
|
387 protected void close() throws IOException { |
|
388 shutdownEntry(); |
|
389 if (connection.isOpen()) |
|
390 connection.close(); |
|
391 } |
|
392 |
|
393 /** |
|
394 * Shuts down the connection in this pool entry. |
|
395 */ |
|
396 protected void shutdown() throws IOException { |
|
397 shutdownEntry(); |
|
398 if (connection.isOpen()) |
|
399 connection.shutdown(); |
|
400 } |
|
401 |
|
402 } |
|
403 |
|
404 /** |
|
405 * The connection adapter used by this manager. |
|
406 */ |
|
407 protected class ConnAdapter extends AbstractPooledConnAdapter { |
|
408 |
|
409 /** |
|
410 * Creates a new connection adapter. |
|
411 * |
|
412 * @param entry the pool entry for the connection being wrapped |
|
413 * @param route the planned route for this connection |
|
414 */ |
|
415 protected ConnAdapter(PoolEntry entry, HttpRoute route) { |
|
416 super(SingleClientConnManager.this, entry); |
|
417 markReusable(); |
|
418 entry.route = route; |
|
419 } |
|
420 |
|
421 } |
|
422 |
|
423 } |