1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.http.impl.client.integration;
28
29 import java.io.IOException;
30 import java.net.ConnectException;
31 import java.net.InetSocketAddress;
32 import java.util.concurrent.CountDownLatch;
33 import java.util.concurrent.TimeUnit;
34 import java.util.concurrent.atomic.AtomicReference;
35
36 import org.apache.http.HttpClientConnection;
37 import org.apache.http.HttpException;
38 import org.apache.http.HttpHost;
39 import org.apache.http.HttpRequest;
40 import org.apache.http.HttpResponse;
41 import org.apache.http.HttpStatus;
42 import org.apache.http.ProtocolVersion;
43 import org.apache.http.client.HttpClient;
44 import org.apache.http.client.methods.HttpGet;
45 import org.apache.http.client.protocol.HttpClientContext;
46 import org.apache.http.concurrent.Cancellable;
47 import org.apache.http.conn.ConnectionPoolTimeoutException;
48 import org.apache.http.conn.ConnectionRequest;
49 import org.apache.http.conn.HttpClientConnectionManager;
50 import org.apache.http.conn.routing.HttpRoute;
51 import org.apache.http.entity.StringEntity;
52 import org.apache.http.impl.client.HttpClients;
53 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
54 import org.apache.http.localserver.LocalServerTestBase;
55 import org.apache.http.message.BasicHeader;
56 import org.apache.http.protocol.BasicHttpContext;
57 import org.apache.http.protocol.HttpContext;
58 import org.apache.http.protocol.HttpRequestHandler;
59 import org.apache.http.protocol.UriHttpRequestHandlerMapper;
60 import org.junit.Assert;
61 import org.junit.Test;
62 import org.mockito.Mockito;
63
64
65
66
67 @SuppressWarnings("static-access")
68 public class TestAbortHandling extends LocalServerTestBase {
69
70 @Test
71 public void testAbortRetry_HTTPCLIENT_1120() throws Exception {
72 final CountDownLatch wait = new CountDownLatch(1);
73
74 this.serverBootstrap.registerHandler("*", new HttpRequestHandler() {
75 @Override
76 public void handle(final HttpRequest request, final HttpResponse response,
77 final HttpContext context) throws HttpException, IOException {
78 try {
79 wait.countDown();
80 Thread.sleep(2000);
81 response.setStatusCode(HttpStatus.SC_OK);
82 final StringEntity entity = new StringEntity("Whatever");
83 response.setEntity(entity);
84 } catch (final Exception e) {
85 response.setStatusCode(HttpStatus.SC_REQUEST_TIMEOUT);
86 }
87 }
88 });
89
90 final HttpHost target = start();
91 final HttpGet httpget = new HttpGet("/");
92
93 final Thread t = new Thread() {
94 @Override
95 public void run(){
96 try {
97 wait.await();
98 } catch (final InterruptedException e) {
99 }
100 httpget.abort();
101 }
102 };
103
104 t.start();
105
106 final HttpClientContext context = HttpClientContext.create();
107 try {
108 this.httpclient.execute(target, httpget, context);
109 } catch (final IllegalStateException e) {
110 } catch (final IOException e) {
111 }
112
113 final HttpRequest reqWrapper = context.getRequest();
114 Assert.assertNotNull("Request should exist",reqWrapper);
115 }
116
117 @Test
118 public void testAbortInAllocate() throws Exception {
119 final CountDownLatch connLatch = new CountDownLatch(1);
120 final CountDownLatch awaitLatch = new CountDownLatch(1);
121 final ConMan conMan = new ConMan(connLatch, awaitLatch);
122 final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
123 final CountDownLatch getLatch = new CountDownLatch(1);
124 this.clientBuilder.setConnectionManager(conMan);
125 final HttpContext context = new BasicHttpContext();
126 final HttpGet httpget = new HttpGet("http://www.example.com/a");
127
128 start();
129
130 new Thread(new Runnable() {
131 @Override
132 public void run() {
133 try {
134 httpclient.execute(httpget, context);
135 } catch(final Throwable t) {
136 throwableRef.set(t);
137 } finally {
138 getLatch.countDown();
139 }
140 }
141 }).start();
142
143 Assert.assertTrue("should have tried to get a connection", connLatch.await(1, TimeUnit.SECONDS));
144
145 httpget.abort();
146
147 Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
148 Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
149 throwableRef.get() instanceof IOException);
150 Assert.assertTrue("cause should be InterruptedException, was: " + throwableRef.get().getCause(),
151 throwableRef.get().getCause() instanceof InterruptedException);
152 }
153
154
155
156
157
158 @Test
159 public void testAbortAfterAllocateBeforeRequest() throws Exception {
160 this.serverBootstrap.registerHandler("*", new BasicService());
161
162 final CountDownLatch releaseLatch = new CountDownLatch(1);
163 final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
164 final CountDownLatch getLatch = new CountDownLatch(1);
165 final HttpContext context = new BasicHttpContext();
166 final HttpGet httpget = new CustomGet("a", releaseLatch);
167
168 final HttpHost target = start();
169
170 new Thread(new Runnable() {
171 @Override
172 public void run() {
173 try {
174 httpclient.execute(target, httpget, context);
175 } catch(final Throwable t) {
176 throwableRef.set(t);
177 } finally {
178 getLatch.countDown();
179 }
180 }
181 }).start();
182
183 Thread.sleep(100);
184
185 httpget.abort();
186
187 releaseLatch.countDown();
188
189 Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
190 Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
191 throwableRef.get() instanceof IOException);
192 }
193
194
195
196
197
198 @Test
199 public void testAbortBeforeExecute() throws Exception {
200 this.serverBootstrap.registerHandler("*", new BasicService());
201
202 final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
203 final CountDownLatch getLatch = new CountDownLatch(1);
204 final CountDownLatch startLatch = new CountDownLatch(1);
205 final HttpContext context = new BasicHttpContext();
206 final HttpGet httpget = new HttpGet("a");
207
208 final HttpHost target = start();
209
210 new Thread(new Runnable() {
211 @Override
212 public void run() {
213 try {
214 try {
215 if(!startLatch.await(1, TimeUnit.SECONDS)) {
216 throw new RuntimeException("Took too long to start!");
217 }
218 } catch(final InterruptedException interrupted) {
219 throw new RuntimeException("Never started!", interrupted);
220 }
221 httpclient.execute(target, httpget, context);
222 } catch(final Throwable t) {
223 throwableRef.set(t);
224 } finally {
225 getLatch.countDown();
226 }
227 }
228 }).start();
229
230 httpget.abort();
231 startLatch.countDown();
232
233 Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
234 Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
235 throwableRef.get() instanceof IOException);
236 }
237
238
239
240
241
242
243 @Test
244 public void testAbortAfterRedirectedRoute() throws Exception {
245 final UriHttpRequestHandlerMapper reqistry = new UriHttpRequestHandlerMapper();
246 this.serverBootstrap.setHandlerMapper(reqistry);
247
248 final CountDownLatch connLatch = new CountDownLatch(1);
249 final CountDownLatch awaitLatch = new CountDownLatch(1);
250 final ConnMan4 conMan = new ConnMan4(connLatch, awaitLatch);
251 final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
252 final CountDownLatch getLatch = new CountDownLatch(1);
253 this.clientBuilder.setConnectionManager(conMan);
254 final HttpContext context = new BasicHttpContext();
255 final HttpGet httpget = new HttpGet("a");
256
257 final HttpHost target = start();
258 reqistry.register("*", new BasicRedirectService(target.getPort()));
259
260 new Thread(new Runnable() {
261 @Override
262 public void run() {
263 try {
264 final HttpHost host = new HttpHost("127.0.0.1", target.getPort());
265 httpclient.execute(host, httpget, context);
266 } catch(final Throwable t) {
267 throwableRef.set(t);
268 } finally {
269 getLatch.countDown();
270 }
271 }
272 }).start();
273
274 Assert.assertTrue("should have tried to get a connection", connLatch.await(1, TimeUnit.SECONDS));
275
276 httpget.abort();
277
278 Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
279 Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
280 throwableRef.get() instanceof IOException);
281 Assert.assertTrue("cause should be InterruptedException, was: " + throwableRef.get().getCause(),
282 throwableRef.get().getCause() instanceof InterruptedException);
283 }
284
285
286
287
288
289
290 @Test
291 public void testSocketConnectFailureReleasesConnection() throws Exception {
292 final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class);
293 final ConnectionRequest connrequest = Mockito.mock(ConnectionRequest.class);
294 Mockito.when(connrequest.get(
295 Mockito.anyInt(), Mockito.any(TimeUnit.class))).thenReturn(conn);
296 final HttpClientConnectionManager connmgr = Mockito.mock(HttpClientConnectionManager.class);
297 Mockito.doThrow(new ConnectException()).when(connmgr).connect(
298 Mockito.any(HttpClientConnection.class),
299 Mockito.any(HttpRoute.class),
300 Mockito.anyInt(),
301 Mockito.any(HttpContext.class));
302
303 Mockito.when(connmgr.requestConnection(
304 Mockito.any(HttpRoute.class), Mockito.any())).thenReturn(connrequest);
305
306 final HttpClient client = HttpClients.custom().setConnectionManager(connmgr).build();
307 final HttpContext context = new BasicHttpContext();
308 final HttpGet httpget = new HttpGet("http://www.example.com/a");
309
310 try {
311 client.execute(httpget, context);
312 Assert.fail("expected IOException");
313 } catch(final IOException expected) {}
314
315 Mockito.verify(connmgr).releaseConnection(conn, null, 0, TimeUnit.MILLISECONDS);
316 }
317
318 private static class BasicService implements HttpRequestHandler {
319 @Override
320 public void handle(final HttpRequest request,
321 final HttpResponse response,
322 final HttpContext context) throws HttpException, IOException {
323 response.setStatusCode(200);
324 response.setEntity(new StringEntity("Hello World"));
325 }
326 }
327
328 private static class BasicRedirectService implements HttpRequestHandler {
329 private final int statuscode = HttpStatus.SC_SEE_OTHER;
330 private final int port;
331
332 public BasicRedirectService(final int port) {
333 this.port = port;
334 }
335
336 @Override
337 public void handle(final HttpRequest request,
338 final HttpResponse response, final HttpContext context)
339 throws HttpException, IOException {
340 final ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
341 response.setStatusLine(ver, this.statuscode);
342 response.addHeader(new BasicHeader("Location", "http://localhost:"
343 + this.port + "/newlocation/"));
344 response.addHeader(new BasicHeader("Connection", "close"));
345 }
346 }
347
348 private static class ConnMan4 extends PoolingHttpClientConnectionManager {
349 private final CountDownLatch connLatch;
350 private final CountDownLatch awaitLatch;
351
352 public ConnMan4(final CountDownLatch connLatch, final CountDownLatch awaitLatch) {
353 super();
354 this.connLatch = connLatch;
355 this.awaitLatch = awaitLatch;
356 }
357
358 @Override
359 public ConnectionRequest requestConnection(final HttpRoute route, final Object state) {
360
361
362 if(route.getTargetHost().getHostName().equals("localhost")) {
363 final Thread currentThread = Thread.currentThread();
364
365 return new ConnectionRequest() {
366
367 @Override
368 public boolean cancel() {
369 currentThread.interrupt();
370 return true;
371 }
372
373 @Override
374 public HttpClientConnection get(
375 final long timeout,
376 final TimeUnit timeUnit) throws InterruptedException, ConnectionPoolTimeoutException {
377 connLatch.countDown();
378
379
380 if(!awaitLatch.await(timeout > 0 ? timeout : Integer.MAX_VALUE, timeUnit)) {
381 throw new ConnectionPoolTimeoutException();
382 }
383
384 return Mockito.mock(HttpClientConnection.class);
385 }
386 };
387 }
388 return super.requestConnection(route, state);
389 }
390 }
391
392
393 static class ConMan implements HttpClientConnectionManager {
394 private final CountDownLatch connLatch;
395 private final CountDownLatch awaitLatch;
396
397 public ConMan(final CountDownLatch connLatch, final CountDownLatch awaitLatch) {
398 this.connLatch = connLatch;
399 this.awaitLatch = awaitLatch;
400 }
401
402 @Override
403 public void closeIdleConnections(final long idletime, final TimeUnit timeUnit) {
404 throw new UnsupportedOperationException("just a mockup");
405 }
406
407 @Override
408 public void closeExpiredConnections() {
409 throw new UnsupportedOperationException("just a mockup");
410 }
411
412 public HttpClientConnection getConnection(final HttpRoute route,
413 final long timeout, final TimeUnit timeUnit) {
414 throw new UnsupportedOperationException("just a mockup");
415 }
416
417 @Override
418 public ConnectionRequest requestConnection(
419 final HttpRoute route,
420 final Object state) {
421
422 final Thread currentThread = Thread.currentThread();
423
424 return new ConnectionRequest() {
425
426 @Override
427 public boolean cancel() {
428 currentThread.interrupt();
429 return true;
430 }
431
432 @Override
433 public HttpClientConnection get(
434 final long timeout,
435 final TimeUnit timeUnit) throws InterruptedException, ConnectionPoolTimeoutException {
436 connLatch.countDown();
437
438
439 if(!awaitLatch.await(timeout > 0 ? timeout : Integer.MAX_VALUE, timeUnit)) {
440 throw new ConnectionPoolTimeoutException();
441 }
442
443 return Mockito.mock(HttpClientConnection.class);
444 }
445
446 };
447 }
448
449 @Override
450 public void shutdown() {
451 }
452
453 public void close() {
454 }
455
456 @Override
457 public void releaseConnection(
458 final HttpClientConnection conn,
459 final Object newState,
460 final long validDuration, final TimeUnit timeUnit) {
461 throw new UnsupportedOperationException("just a mockup");
462 }
463
464 @Override
465 public void connect(
466 final HttpClientConnection conn,
467 final HttpRoute route,
468 final int connectTimeout,
469 final HttpContext context) throws IOException {
470 throw new UnsupportedOperationException("just a mockup");
471 }
472
473 @Override
474 public void upgrade(
475 final HttpClientConnection conn,
476 final HttpRoute route,
477 final HttpContext context) throws IOException {
478 throw new UnsupportedOperationException("just a mockup");
479 }
480
481 @Override
482 public void routeComplete(
483 final HttpClientConnection conn,
484 final HttpRoute route,
485 final HttpContext context) throws IOException {
486 throw new UnsupportedOperationException("just a mockup");
487 }
488
489 public void connect(
490 final HttpClientConnection conn,
491 final HttpHost host,
492 final InetSocketAddress localAddress,
493 final int connectTimeout,
494 final HttpContext context) {
495 throw new UnsupportedOperationException("just a mockup");
496 }
497
498 public void upgrade(
499 final HttpClientConnection conn,
500 final HttpHost host,
501 final HttpContext context) {
502 throw new UnsupportedOperationException("just a mockup");
503 }
504 }
505
506 private static class CustomGet extends HttpGet {
507 private final CountDownLatch releaseTriggerLatch;
508
509 public CustomGet(final String uri, final CountDownLatch releaseTriggerLatch) {
510 super(uri);
511 this.releaseTriggerLatch = releaseTriggerLatch;
512 }
513
514 @Override
515 public void setCancellable(final Cancellable cancellable) {
516 try {
517 if(!releaseTriggerLatch.await(1, TimeUnit.SECONDS)) {
518 throw new RuntimeException("Waited too long...");
519 }
520 } catch(final InterruptedException ie) {
521 throw new RuntimeException(ie);
522 }
523
524 super.setCancellable(cancellable);
525 }
526
527 }
528
529 }