View Javadoc

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  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   *  Tests for Abort handling.
66   */
67  @SuppressWarnings("static-access") // test code
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(); // trigger abort
80                      Thread.sleep(2000); // allow time for abort to happen
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      * Tests that an abort called after the connection has been retrieved
156      * but before a release trigger is set does still abort the request.
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); // Give it a little time to proceed to release...
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      * Tests that an abort called completely before execute
196      * still aborts the request.
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      * Tests that an abort called after a redirect has found a new host
240      * still aborts in the correct place (while trying to get the new
241      * host's route, not while doing the subsequent request).
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      * Tests that if a socket fails to connect, the allocated connection is
288      * properly released back to the connection manager.
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             // If this is the redirect route, stub the return value
361             // so-as to pretend the host is waiting on a slot...
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 tunit) throws InterruptedException, ConnectionPoolTimeoutException {
377                         connLatch.countDown(); // notify waiter that we're getting a connection
378 
379                         // zero usually means sleep forever, but CountDownLatch doesn't interpret it that way.
380                         if(!awaitLatch.await(timeout > 0 ? timeout : Integer.MAX_VALUE, tunit)) {
381                             throw new ConnectionPoolTimeoutException();
382                         }
383 
384                         return Mockito.mock(HttpClientConnection.class);
385                     }
386                 };
387             } else {
388                 return super.requestConnection(route, state);
389             }
390         }
391     }
392 
393 
394     static class ConMan implements HttpClientConnectionManager {
395         private final CountDownLatch connLatch;
396         private final CountDownLatch awaitLatch;
397 
398         public ConMan(final CountDownLatch connLatch, final CountDownLatch awaitLatch) {
399             this.connLatch = connLatch;
400             this.awaitLatch = awaitLatch;
401         }
402 
403         @Override
404         public void closeIdleConnections(final long idletime, final TimeUnit tunit) {
405             throw new UnsupportedOperationException("just a mockup");
406         }
407 
408         @Override
409         public void closeExpiredConnections() {
410             throw new UnsupportedOperationException("just a mockup");
411         }
412 
413         public HttpClientConnection getConnection(final HttpRoute route,
414                 final long timeout, final TimeUnit tunit) {
415             throw new UnsupportedOperationException("just a mockup");
416         }
417 
418         @Override
419         public ConnectionRequest requestConnection(
420                 final HttpRoute route,
421                 final Object state) {
422 
423             final Thread currentThread = Thread.currentThread();
424 
425             return new ConnectionRequest() {
426 
427                 @Override
428                 public boolean cancel() {
429                     currentThread.interrupt();
430                     return true;
431                 }
432 
433                 @Override
434                 public HttpClientConnection get(
435                         final long timeout,
436                         final TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException {
437                     connLatch.countDown(); // notify waiter that we're getting a connection
438 
439                     // zero usually means sleep forever, but CountDownLatch doesn't interpret it that way.
440                     if(!awaitLatch.await(timeout > 0 ? timeout : Integer.MAX_VALUE, tunit)) {
441                         throw new ConnectionPoolTimeoutException();
442                     }
443 
444                     return Mockito.mock(HttpClientConnection.class);
445                 }
446 
447             };
448         }
449 
450         @Override
451         public void shutdown() {
452         }
453 
454         public void close() {
455         }
456 
457         @Override
458         public void releaseConnection(
459                 final HttpClientConnection conn,
460                 final Object newState,
461                 final long validDuration, final TimeUnit timeUnit) {
462             throw new UnsupportedOperationException("just a mockup");
463         }
464 
465         @Override
466         public void connect(
467                 final HttpClientConnection conn,
468                 final HttpRoute route,
469                 final int connectTimeout,
470                 final HttpContext context) throws IOException {
471             throw new UnsupportedOperationException("just a mockup");
472         }
473 
474         @Override
475         public void upgrade(
476                 final HttpClientConnection conn,
477                 final HttpRoute route,
478                 final HttpContext context) throws IOException {
479             throw new UnsupportedOperationException("just a mockup");
480         }
481 
482         @Override
483         public void routeComplete(
484                 final HttpClientConnection conn,
485                 final HttpRoute route,
486                 final HttpContext context) throws IOException {
487             throw new UnsupportedOperationException("just a mockup");
488         }
489 
490         public void connect(
491                 final HttpClientConnection conn,
492                 final HttpHost host,
493                 final InetSocketAddress localAddress,
494                 final int connectTimeout,
495                 final HttpContext context) {
496             throw new UnsupportedOperationException("just a mockup");
497         }
498 
499         public void upgrade(
500                 final HttpClientConnection conn,
501                 final HttpHost host,
502                 final HttpContext context) {
503             throw new UnsupportedOperationException("just a mockup");
504         }
505     }
506 
507     private static class CustomGet extends HttpGet {
508         private final CountDownLatch releaseTriggerLatch;
509 
510         public CustomGet(final String uri, final CountDownLatch releaseTriggerLatch) {
511             super(uri);
512             this.releaseTriggerLatch = releaseTriggerLatch;
513         }
514 
515         @Override
516         public void setCancellable(final Cancellable cancellable) {
517             try {
518                 if(!releaseTriggerLatch.await(1, TimeUnit.SECONDS)) {
519                     throw new RuntimeException("Waited too long...");
520                 }
521             } catch(final InterruptedException ie) {
522                 throw new RuntimeException(ie);
523             }
524 
525             super.setCancellable(cancellable);
526         }
527 
528     }
529 
530 }