1   /*
2    * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestHttpConnectionManager.java,v 1.8.2.5 2004/03/28 21:06:24 mbecke Exp $
3    * $Revision: 1.8.2.5 $
4    * $Date: 2004/03/28 21:06:24 $
5    * ====================================================================
6    *
7    *  Copyright 1999-2004 The Apache Software Foundation
8    *
9    *  Licensed under the Apache License, Version 2.0 (the "License");
10   *  you may not use this file except in compliance with the License.
11   *  You may obtain a copy of the License at
12   *
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   *
15   *  Unless required by applicable law or agreed to in writing, software
16   *  distributed under the License is distributed on an "AS IS" BASIS,
17   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   *  See the License for the specific language governing permissions and
19   *  limitations under the License.
20   * ====================================================================
21   *
22   * This software consists of voluntary contributions made by many
23   * individuals on behalf of the Apache Software Foundation.  For more
24   * information on the Apache Software Foundation, please see
25   * <http://www.apache.org/>.
26   *
27   * [Additional notices, if required by prior licensing conditions]
28   *
29   */
30  
31  package org.apache.commons.httpclient;
32  
33  import java.io.IOException;
34  import java.lang.ref.WeakReference;
35  
36  import junit.framework.Test;
37  import junit.framework.TestSuite;
38  
39  import org.apache.commons.httpclient.methods.GetMethod;
40  
41  /***
42   * Unit tests for {@link HttpConnectionManager}.
43   *
44   * @author Marc A. Saegesser
45   * @version $Id: TestHttpConnectionManager.java,v 1.8.2.5 2004/03/28 21:06:24 mbecke Exp $
46   */
47  public class TestHttpConnectionManager extends TestLocalHostBase {
48  
49      // ------------------------------------------------------------ Constructor
50      public TestHttpConnectionManager(String testName) {
51          super(testName);
52      }
53  
54      // ------------------------------------------------------------------- Main
55      public static void main(String args[]) {
56          String[] testCaseName = { TestHttpConnectionManager.class.getName() };
57          junit.textui.TestRunner.main(testCaseName);
58      }
59  
60      // ------------------------------------------------------- TestCase Methods
61  
62      public static Test suite() {
63          return new TestSuite(TestHttpConnectionManager.class);
64      }
65  
66  
67      // ----------------------------------------------------------- Test Methods
68  
69      // Test the accessor methods
70      public void testMaxConnectionsAccessors() {
71          MultiThreadedHttpConnectionManager mgr = new MultiThreadedHttpConnectionManager();
72  
73          // First test the default value
74          assertEquals("Default MaxConnections", 2, mgr.getMaxConnectionsPerHost());
75  
76          mgr.setMaxConnectionsPerHost(10);
77          assertEquals("MaxConnections", 10, mgr.getMaxConnectionsPerHost());
78      }
79  
80      /***
81       * Test that the ConnectMethod correctly releases connections when
82       * CONNECT fails.
83       */
84      public void testConnectMethodFailureRelease() {
85          
86          MultiThreadedHttpConnectionManager mgr = new MultiThreadedHttpConnectionManager();
87          mgr.setMaxTotalConnections(1);
88          
89          // we're going to execute a connect method against the localhost, assuming
90          // that CONNECT is not supported.  This should test the fakeResponse()
91          // code on HttpMethodBase.
92          HostConfiguration hostConfiguration = new HostConfiguration();
93          hostConfiguration.setHost(getHost(), getPort(), getProtocol());
94          
95          GetMethod get = new GetMethod("/");
96          try {
97              HttpConnection connection = mgr.getConnection(hostConfiguration);
98              ConnectMethod connect = new ConnectMethod(get);
99              assertTrue(connect.execute(new HttpState(), connection) != 200);
100         } catch (IOException e) {
101             e.printStackTrace();
102             fail("Error executing connect: " + e);
103         }
104 
105         // this should calling releaseConnection() releases the connection
106         try {
107             get.releaseConnection();
108             mgr.getConnection(hostConfiguration, 1).releaseConnection();
109         } catch (HttpException e1) {
110             fail("Connection should have been available.");
111         }
112         
113         get = new GetMethod("/");
114         
115         try {
116             HttpConnection connection = mgr.getConnection(hostConfiguration);
117             ConnectMethod connect = new ConnectMethod(get);                        
118             assertTrue(connect.execute(new HttpState(), connection) != 200);
119         } catch (IOException e) {
120             e.printStackTrace();
121             fail("Error executing connect: " + e);
122         }
123 
124         // make sure reading the response fully releases the connection        
125         try {
126             get.getResponseBodyAsString();
127             mgr.getConnection(hostConfiguration, 1).releaseConnection();
128         } catch (HttpException e1) {
129             fail("Connection should have been available.");
130         }     
131         
132         get = new GetMethod("/");
133         
134         try {
135             HttpConnection connection = mgr.getConnection(hostConfiguration);
136             ConnectMethod connect = new ConnectMethod(get);                        
137             assertTrue(connect.execute(new HttpState(), connection) != 200);
138         } catch (IOException e) {
139             e.printStackTrace();
140             fail("Error executing connect: " + e);
141         }
142 
143         // make sure closing the output stream releases the connection        
144         try {
145             get.getResponseBodyAsStream().close();
146             mgr.getConnection(hostConfiguration, 1).releaseConnection();
147         } catch (HttpException e) {
148             fail("Connection should have been available.");
149         } catch (IOException e) {
150             e.printStackTrace();
151             fail("Close connection failed: " + e);   
152         }
153     }
154 
155     public void testGetConnection() {
156         MultiThreadedHttpConnectionManager mgr = new MultiThreadedHttpConnectionManager();
157 
158         HostConfiguration hostConfiguration = new HostConfiguration();
159         hostConfiguration.setHost("www.nosuchserver.com", 80, "http");
160 
161         // Create a new connection
162         HttpConnection conn = mgr.getConnection(hostConfiguration);
163         // Validate the connection properties
164         assertEquals("Host", "www.nosuchserver.com", conn.getHost());
165         assertEquals("Port", 80, conn.getPort());
166         // Release the connection
167         mgr.releaseConnection(conn);
168 
169         // Create a new connection
170         hostConfiguration.setHost("www.nosuchserver.com", -1, "https");
171         conn = mgr.getConnection(hostConfiguration);
172         // Validate the connection properties
173         assertEquals("Host", "www.nosuchserver.com", conn.getHost());
174         assertEquals("Port", 443, conn.getPort());
175         // Release the connection
176         mgr.releaseConnection(conn);
177 
178         // Create a new connection
179         hostConfiguration.setHost("www.nowhere.org", 8080, "http");
180         conn = mgr.getConnection(hostConfiguration);
181         // Validate the connection properties
182         assertEquals("Host", "www.nowhere.org", conn.getHost());
183         assertEquals("Port", 8080, conn.getPort());
184         // Release the connection
185         mgr.releaseConnection(conn);
186 
187     }
188 
189     public void testDroppedThread() throws Exception {
190 
191         MultiThreadedHttpConnectionManager mthcm = new MultiThreadedHttpConnectionManager();
192         HttpClient httpClient = createHttpClient(mthcm);
193         WeakReference wr = new WeakReference(mthcm);
194 
195         GetMethod method = new GetMethod("/");
196         httpClient.executeMethod(method);
197         method.releaseConnection();
198 
199         mthcm = null;
200         httpClient = null;
201         method = null;
202         
203         System.gc();
204 
205         // this sleep appears to be necessary in order to give the JVM
206         // time to clean up the miscellaneous pointers to the connection manager
207         try {
208             Thread.sleep(1000);
209         } catch (InterruptedException e) {
210             fail("shouldn't be interrupted.");
211         }
212 
213         Object connectionManager = wr.get();
214         assertNull("connectionManager should be null", connectionManager);
215     }    
216     
217     public void testWriteRequestReleaseConnection() {
218 
219         MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
220         connectionManager.setMaxConnectionsPerHost(1);
221 
222         HttpClient client = createHttpClient(connectionManager);
223         
224         GetMethod get = new GetMethod("/") {
225             protected boolean writeRequestBody(HttpState state, HttpConnection conn)
226                 throws IOException, HttpException {
227                 throw new IOException("Oh no!!");
228             }
229         };
230         
231         try {
232             client.executeMethod(get);
233             fail("An exception should have occurred.");
234         } catch (HttpException e) {
235             e.printStackTrace();
236             fail("HttpException should not have occurred: " + e);
237         } catch (IOException e) {
238             // expected
239         }
240         
241         try {
242             connectionManager.getConnection(client.getHostConfiguration(), 1);
243         } catch (HttpException e) {
244             e.printStackTrace();
245             fail("Connection was not released: " + e);
246         }
247         
248     }
249     
250     public void testReleaseConnection() {
251 
252         MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
253         connectionManager.setMaxConnectionsPerHost(1);
254 
255         HttpClient client = createHttpClient(connectionManager);
256         // we shouldn't have to wait if a connection is available
257         client.setHttpConnectionFactoryTimeout( 1 );
258 
259         GetMethod getMethod = new GetMethod("/");
260 
261         try {
262             client.executeMethod(getMethod);
263         } catch (Exception e) {
264             fail("error reading from server: " + e);
265         }
266 
267         try {
268             // this should fail quickly since the connection has not been released
269             client.executeMethod(getMethod);
270             fail("a httpConnection should not be available");
271         } catch (HttpException e) {            
272         } catch (IOException e) {
273             fail("error reading from server; " + e);
274         }
275 
276         // this should release the connection
277         getMethod.releaseConnection();
278 
279         getMethod = new GetMethod("/");
280 
281         try {
282             // this should fail quickly if the connection has not been released
283             client.executeMethod(getMethod);
284         } catch (HttpException e) {
285             fail("httpConnection does not appear to have been released: " + e);
286         } catch (IOException e) {
287             fail("error reading from server; " + e);
288         }
289 
290     }
291 
292     /***
293      * Makes sure that a connection gets released after the content of the body
294      * is read.
295      */
296     public void testResponseAutoRelease() {
297 
298         MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
299         connectionManager.setMaxConnectionsPerHost(1);
300 
301         HttpClient client = createHttpClient(connectionManager);
302         // we shouldn't have to wait if a connection is available
303         client.setHttpConnectionFactoryTimeout( 1 );
304 
305         GetMethod getMethod = new GetMethod("/");
306 
307         try {
308             client.executeMethod(getMethod);
309         } catch (Exception e) {
310             fail("error reading from server: " + e);
311         }
312         
313         // this should release the connection
314         getMethod.getResponseBody();
315 
316         getMethod = new GetMethod("/");
317 
318         try {
319             // this should fail quickly if the connection has not been released
320             client.executeMethod(getMethod);
321         } catch (HttpException e) {
322             fail("httpConnection does not appear to have been released: " + e);
323         } catch (IOException e) {
324             fail("error reading from server; " + e);
325         }
326 
327     }
328     
329     /***
330      * Tests the MultiThreadedHttpConnectionManager's ability to reclaim unused 
331      * connections.
332      */
333     public void testConnectionReclaiming() {
334         
335         MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
336         connectionManager.setMaxConnectionsPerHost(1);
337         connectionManager.setMaxTotalConnections(1);
338 
339         HostConfiguration host1 = new HostConfiguration();
340         host1.setHost("host1", -1, "http");
341 
342         HostConfiguration host2 = new HostConfiguration();
343         host2.setHost("host2", -1, "http");
344 
345         HttpConnection connection = connectionManager.getConnection(host1);
346         // now release this connection
347         connection.releaseConnection();
348         connection = null;
349         
350         try {
351             // the connection from host1 should be reclaimed
352             connection = connectionManager.getConnection(host2, 100);
353         } catch (HttpException e) {
354             e.printStackTrace();
355             fail("a httpConnection should have been available: " + e);
356         }        
357     }
358     
359     /***
360      * Tests that {@link MultiThreadedHttpConnectionManager#shutdownAll()} closes all resources
361      * and makes all connection mangers unusable.
362      */
363     public void testShutdownAll() {
364 
365         MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
366         connectionManager.setMaxConnectionsPerHost(1);
367         connectionManager.setMaxTotalConnections(1);
368 
369         HostConfiguration host1 = new HostConfiguration();
370         host1.setHost("host1", -1, "http");
371 
372         // hold on to the only connection
373         HttpConnection connection = connectionManager.getConnection(host1);
374 
375         // wait for a connection on another thread
376         GetConnectionThread getConn = new GetConnectionThread(host1, connectionManager, 0);
377         getConn.start();
378         
379         MultiThreadedHttpConnectionManager.shutdownAll();
380         
381         // now release this connection, this should close the connection, but have no other effect
382         connection.releaseConnection();
383         connection = null;
384         
385         try {
386             getConn.join();
387         } catch (InterruptedException e) {
388             e.printStackTrace();
389         }
390         
391         // this thread should have caught an exception without getting a connection
392         assertNull("Not connection should have been checked out", getConn.getConnection());
393         assertNotNull("There should have been an exception", getConn.getException());
394         
395         try {
396             connectionManager.getConnection(host1);
397             fail("An exception should have occurred");
398         } catch (Exception e) {
399             // this is expected
400         }
401     }
402         
403     /***
404      * Tests that {@link MultiThreadedHttpConnectionManager#shutdown()} closes all resources
405      * and makes the connection manger unusable.
406      */
407     public void testShutdown() {
408 
409         MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
410         connectionManager.setMaxConnectionsPerHost(1);
411         connectionManager.setMaxTotalConnections(1);
412 
413         HostConfiguration host1 = new HostConfiguration();
414         host1.setHost("host1", -1, "http");
415 
416         // hold on to the only connection
417         HttpConnection connection = connectionManager.getConnection(host1);
418 
419         // wait for a connection on another thread
420         GetConnectionThread getConn = new GetConnectionThread(host1, connectionManager, 0);
421         getConn.start();
422         
423         connectionManager.shutdown();
424         
425         // now release this connection, this should close the connection, but have no other effect
426         connection.releaseConnection();
427         connection = null;
428         
429         try {
430             getConn.join();
431         } catch (InterruptedException e) {
432             e.printStackTrace();
433         }
434         
435         // this thread should have caught an exception without getting a connection
436         assertNull("Not connection should have been checked out", getConn.getConnection());
437         assertNotNull("There should have been an exception", getConn.getException());
438         
439         try {
440             connectionManager.getConnection(host1);
441             fail("An exception should have occurred");
442         } catch (Exception e) {
443             // this is expected
444         }
445     }
446     
447     /***
448      * Tests the MultiThreadedHttpConnectionManager's ability to restrict the maximum number 
449      * of connections.
450      */    
451     public void testMaxConnections() {
452         
453         MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
454         connectionManager.setMaxConnectionsPerHost(1);
455         connectionManager.setMaxTotalConnections(2);
456 
457         HostConfiguration host1 = new HostConfiguration();
458         host1.setHost("host1", -1, "http");
459 
460         HostConfiguration host2 = new HostConfiguration();
461         host2.setHost("host2", -1, "http");
462 
463         HttpConnection connection1 = connectionManager.getConnection(host1);
464         HttpConnection connection2 = connectionManager.getConnection(host2);
465     
466         try {
467             // this should fail quickly since the connection has not been released
468             connectionManager.getConnection(host2, 100);
469             fail("a httpConnection should not be available");
470         } catch (HttpException e) {
471             // this should throw an exception
472         }
473         
474         // release one of the connections
475         connection2.releaseConnection();
476         connection2 = null;
477         
478         try {
479             // there should be a connection available now
480             connection2 = connectionManager.getConnection(host2, 100);
481         } catch (HttpException e) {
482             e.printStackTrace();
483             fail("a httpConnection should have been available: " + e);
484         }
485     }    
486 
487     public void testHostReusePreference() {
488         
489         final MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
490         connectionManager.setMaxConnectionsPerHost(1);
491         connectionManager.setMaxTotalConnections(1);
492 
493         final HostConfiguration host1 = new HostConfiguration();
494         host1.setHost("host1", -1, "http");
495 
496         final HostConfiguration host2 = new HostConfiguration();
497         host2.setHost("host2", -1, "http");
498 
499         HttpConnection connection = connectionManager.getConnection(host1);
500 
501         GetConnectionThread getHost1 = new GetConnectionThread(host1, connectionManager, 200);
502         GetConnectionThread getHost2 = new GetConnectionThread(host2, connectionManager, 200);
503         
504         getHost2.start();
505         getHost1.start();
506         
507         // give the threads some time to startup
508         try {
509             Thread.sleep(100);
510         } catch (InterruptedException e1) {
511             e1.printStackTrace();
512         }
513             
514         // after the connection to host1 is released it should be given to getHost1
515         connection.releaseConnection();
516         connection = null;
517 
518         try {
519             getHost1.join();
520             getHost2.join();
521         } catch (InterruptedException e) {
522             e.printStackTrace();
523         }
524 
525         assertNotSame(
526             "Connection should have been given to someone", 
527             getHost1.getConnection(),
528             getHost2.getConnection()
529         );        
530         assertNotNull("Connection should have been given to host1", getHost1.getConnection());
531         assertNull("Connection should NOT have been given to host2", getHost2.getConnection());
532         
533     } 
534     
535     public void testMaxConnectionsPerServer() {
536      
537         MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
538         connectionManager.setMaxConnectionsPerHost(1);
539 
540         HttpClient client = createHttpClient(connectionManager);
541         // we shouldn't have to wait if a connection is available
542         client.setHttpConnectionFactoryTimeout( 1 );
543 
544         GetMethod getMethod = new GetMethod("/");
545 
546         try {
547             client.executeMethod(getMethod);
548         } catch (Exception e) {
549             fail("error reading from server: " + e);
550         }
551 
552         GetMethod getMethod2 = new GetMethod("/");
553 
554         try {
555             // this should fail quickly since the connection has not been released
556             client.executeMethod(getMethod2);
557             fail("a httpConnection should not be available");
558         } catch (HttpException e) {
559         } catch (IOException e) {
560             fail("error reading from server; " + e);
561         }
562                 
563     }
564     
565     public void testReclaimUnusedConnection() {
566 
567         MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
568         connectionManager.setMaxConnectionsPerHost(1);
569 
570         HttpClient client = createHttpClient(connectionManager);
571         // we shouldn't have to wait if a connection is available
572         client.setHttpConnectionFactoryTimeout( 30000 );
573 
574         GetMethod getMethod = new GetMethod("/");
575 
576         try {
577             client.executeMethod(getMethod);
578         } catch (Exception e) {
579             fail("error reading from server: " + e);
580         }
581 
582         getMethod = new GetMethod("/");
583         
584         Runtime.getRuntime().gc();
585 
586         try {
587             // we didn't explicitly release the connection, but it should be 
588             // reclaimed by the garbage collector, we hope:)
589             client.executeMethod(getMethod);
590         } catch (HttpException e) {
591             fail("httpConnection does not appear to have been reclaimed by the GC: " + e);
592         } catch (IOException e) {
593             fail("error reading from server; " + e);
594         }
595 
596     }
597     
598     public void testGetFromMultipleThreads() {
599         
600         HttpClient client = createHttpClient(new MultiThreadedHttpConnectionManager());
601         ExecuteMethodThread[] threads = new ExecuteMethodThread[10];
602         
603         for (int i = 0; i < threads.length; i++) {
604             GetMethod method = new GetMethod("/");
605             method.setFollowRedirects(true);
606             
607             threads[i] = new ExecuteMethodThread(method, client);
608             threads[i].start();
609         }
610         
611         for (int i = 0; i < threads.length; i++) {
612             try {
613                 // wait until this thread finishes. we'll give it 10 seconds,
614                 // but it shouldn't take that long
615                 threads[i].join(10000);
616             } catch (InterruptedException e) {
617             }
618             // make sure an exception did not occur
619             Exception e = threads[i].getException();
620             if (e != null) {
621                 fail("An error occured in the get: " + e);
622             }
623             // we should have a 200 status
624             assertEquals(threads[i].getMethod().getStatusCode(), HttpStatus.SC_OK);
625         }
626     }
627 
628     public void testTimeout() {
629         MultiThreadedHttpConnectionManager mgr = new MultiThreadedHttpConnectionManager();
630         mgr.setMaxConnectionsPerHost(2);
631         
632         try{
633             HostConfiguration hostConfig = new HostConfiguration();
634             hostConfig.setHost("www.nosuchserver.com", 80, "http");
635             
636             HttpConnection conn1 = mgr.getConnection(hostConfig);
637             HttpConnection conn2 = mgr.getConnection(hostConfig);
638             
639             HttpConnection conn3 = mgr.getConnection(hostConfig, 1000);
640             fail("Expected an HttpException.");
641             
642         }catch(HttpException e){
643             //Expected result
644         }
645     }
646     
647     static class GetConnectionThread extends Thread {
648         
649         private HostConfiguration hostConfiguration;
650         private MultiThreadedHttpConnectionManager connectionManager;
651         private HttpConnection connection;
652         private long timeout;
653         private Exception exception;
654         
655         public GetConnectionThread(
656             HostConfiguration hostConfiguration, 
657             MultiThreadedHttpConnectionManager connectionManager,
658             long timeout
659         ) {
660             this.hostConfiguration = hostConfiguration;
661             this.connectionManager = connectionManager; 
662             this.timeout = timeout;
663         }
664         
665         public void run() {
666             try {
667                 connection = connectionManager.getConnection(hostConfiguration, timeout);
668             } catch (Exception e) {
669                 exception = e;
670             }            
671         }
672         
673         public Exception getException() {
674             return exception;
675         }
676         
677         public HttpConnection getConnection() {
678             return connection;
679         }
680 
681     }
682     
683 }
684