Coverage Report - groovyx.net.http.AsyncHTTPBuilder
 
Classes in this File Line Coverage Branch Coverage Complexity
AsyncHTTPBuilder
97%
32/33
50%
5/10
0
AsyncHTTPBuilder$1
40%
2/5
N/A
0
 
 1  
 /*
 2  
  * Copyright 2003-2008 the original author or authors.
 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  
  * You are receiving this code free of charge, which represents many hours of
 17  
  * effort from other individuals and corporations.  As a responsible member 
 18  
  * of the community, you are asked (but not required) to donate any 
 19  
  * enhancements or improvements back to the community under a similar open 
 20  
  * source license.  Thank you. -TMN
 21  
  */
 22  
 package groovyx.net.http;
 23  
 
 24  
 import java.io.IOException;
 25  
 import java.net.URISyntaxException;
 26  
 import java.util.Map;
 27  
 import java.util.concurrent.Callable;
 28  
 import java.util.concurrent.Executors;
 29  
 import java.util.concurrent.Future;
 30  
 import java.util.concurrent.ThreadPoolExecutor;
 31  
 
 32  
 import org.apache.http.HttpVersion;
 33  
 import org.apache.http.client.ClientProtocolException;
 34  
 import org.apache.http.conn.ClientConnectionManager;
 35  
 import org.apache.http.conn.params.ConnManagerParams;
 36  
 import org.apache.http.conn.params.ConnPerRouteBean;
 37  
 import org.apache.http.conn.scheme.PlainSocketFactory;
 38  
 import org.apache.http.conn.scheme.Scheme;
 39  
 import org.apache.http.conn.scheme.SchemeRegistry;
 40  
 import org.apache.http.conn.ssl.SSLSocketFactory;
 41  
 import org.apache.http.impl.client.DefaultHttpClient;
 42  
 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
 43  
 import org.apache.http.params.BasicHttpParams;
 44  
 import org.apache.http.params.HttpParams;
 45  
 import org.apache.http.params.HttpProtocolParams;
 46  
 
 47  
 /**
 48  
  * This implementation makes all requests asynchronous by submitting jobs to a 
 49  
  * {@link ThreadPoolExecutor}.  All request methods (including <code>get</code> 
 50  
  * and <code>post</code>) return a {@link Future} instance, whose 
 51  
  * {@link Future#get() get} method will provide access to whatever value was 
 52  
  * returned from the response handler closure.
 53  
  *  
 54  
  * @author <a href='mailto:tomstrummer+httpbuilder@gmail.com'>Tom Nichols</a>
 55  
  */
 56  32
 public class AsyncHTTPBuilder extends HTTPBuilder {
 57  
 
 58  
         /**
 59  
          * Default pool size is one is not supplied in the constructor.
 60  
          */
 61  
         public static final int DEFAULT_POOL_SIZE = 4;
 62  
         
 63  4
         protected final ThreadPoolExecutor threadPool = 
 64  
                 (ThreadPoolExecutor)Executors.newCachedThreadPool();
 65  
         
 66  
         /**
 67  
          * Accepts the following named parameters:
 68  
          * <dl>
 69  
          *  <dt>poolSize</dt><dd>Max number of concurrent requests</dd>
 70  
          *  <dt>url</dt><dd>Default request URL</dd>
 71  
          *  <dt>contentType</dt><dd>Default content type for requests and responses</dd>
 72  
          * </dl>
 73  
          */
 74  
         public AsyncHTTPBuilder( Map<String, ?> args ) throws URISyntaxException {
 75  4
                 super();
 76  4
                 Object poolSize = args.get("poolSize");
 77  4
                 if ( poolSize == null ) poolSize = DEFAULT_POOL_SIZE;
 78  4
                 this.initThreadPools( (Integer)poolSize );
 79  
                 
 80  4
                 Object defaultURL = args.get("url");
 81  4
                 if ( defaultURL != null ) super.setURL(defaultURL);
 82  
 
 83  4
                 Object defaultContentType = args.get("contentType");
 84  4
                 if ( defaultContentType != null ) 
 85  4
                         super.setContentType(defaultContentType);
 86  4
         }
 87  
         
 88  
         /**
 89  
          * Submits a {@link Callable} instance to the job pool, which in turn will 
 90  
          * call {@link HTTPBuilder#doRequest(SendDelegate)} in an asynchronous 
 91  
          * thread.  The {@link Future} instance returned by this value (which in 
 92  
          * turn should be returned by any of the public <code>request</code> methods
 93  
          * (including <code>get</code> and <code>post</code>) may be used to 
 94  
          * retrieve whatever value may be returned from the executed response 
 95  
          * handler closure. 
 96  
          */
 97  
         @Override
 98  
         protected Future<?> doRequest( final SendDelegate delegate ) {
 99  16
                 return threadPool.submit( new Callable<Object>() {
 100  16
                         /*@Override*/ public Object call() throws Exception {
 101  
                                 try {
 102  15
                                         return doRequestSuper(delegate);
 103  
                                 }
 104  0
                                 catch( Exception ex ) {
 105  0
                                         log.error( "Exception thrown from request delegate: " + 
 106  
                                                         delegate, ex );
 107  0
                                         throw ex;
 108  
                                 }
 109  
                         }
 110  
                 });
 111  
         }
 112  
         
 113  
         /*
 114  
          * Because we can't call "super.doRequest" from within the anonymous 
 115  
          * Callable subclass.
 116  
          */
 117  
         private Object doRequestSuper( SendDelegate delegate ) throws IOException {
 118  16
                 return super.doRequest(delegate);
 119  
         }
 120  
         
 121  
         /**
 122  
          * Initializes threading parameters for the HTTPClient's 
 123  
          * {@link ThreadSafeClientConnManager}, and this class' ThreadPoolExecutor. 
 124  
          */
 125  
         protected void initThreadPools( final int poolSize ) {
 126  4
                 if (poolSize < 1) throw new IllegalArgumentException("poolSize may not be < 1");
 127  
                 // Create and initialize HTTP parameters
 128  4
                 HttpParams params = client != null ? client.getParams()
 129  
                                 : new BasicHttpParams();
 130  4
                 ConnManagerParams.setMaxTotalConnections(params, poolSize);
 131  4
                 ConnManagerParams.setMaxConnectionsPerRoute(params,
 132  
                                 new ConnPerRouteBean(poolSize));
 133  
 
 134  4
                 HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
 135  
 
 136  
                 // Create and initialize scheme registry
 137  4
                 SchemeRegistry schemeRegistry = new SchemeRegistry();
 138  4
                 schemeRegistry.register( new Scheme( "http", 
 139  
                                 PlainSocketFactory.getSocketFactory(), 80 ) );
 140  4
                 schemeRegistry.register( new Scheme( "https", 
 141  
                                 SSLSocketFactory.getSocketFactory(), 443));
 142  
 
 143  4
                 ClientConnectionManager cm = new ThreadSafeClientConnManager(
 144  
                                 params, schemeRegistry );
 145  4
                 super.client = new DefaultHttpClient( cm, params );
 146  
 
 147  
                 /* Although the thread pool is flexible, it cannot become bigger than
 148  
                  * the max size of the connection pool-- otherwise threads will be
 149  
                  * created in this pool for new jobs, but they will all block when
 150  
                  * waiting for a free connection to send the request.
 151  
                  */
 152  4
                 this.threadPool.setMaximumPoolSize(poolSize);
 153  4
         }
 154  
         
 155  
         /**
 156  
          * <p>Access the underlying threadpool to adjust things like job timeouts.</p>  
 157  
          * 
 158  
          * <p>Note that this is not the same thread pool used by the HttpClient's 
 159  
          * {@link ThreadSafeClientConnManager}.  Therefore, increasing the 
 160  
          * {@link ThreadPoolExecutor#setMaximumPoolSize(int) maximum pool size} will
 161  
          * not in turn increase the number of possible concurrent requests.  It will
 162  
          * simply cause more requests to be <i>attempted</i> which will then simply
 163  
          * block while waiting for an available connection.</p>
 164  
          * 
 165  
          * <p>Since {@link ThreadSafeClientConnManager} has no public mechanism to 
 166  
          * adjust its pool size, the value 
 167  
          * @return
 168  
          */
 169  
         public ThreadPoolExecutor getThreadPoolExecutor() {
 170  0
                 return this.threadPool;
 171  
         }
 172  
         
 173  
         /**
 174  
          * {@inheritDoc}
 175  
          */
 176  
         @Override public void shutdown() {
 177  7
                 super.shutdown(); 
 178  7
                 this.threadPool.shutdown();
 179  7
         }
 180  
         
 181  
         /**
 182  
          * {@inheritDoc}
 183  
          * @see #shutdown()
 184  
          */
 185  
         @Override protected void finalize() throws Throwable {
 186  3
                 this.shutdown();
 187  3
                 super.finalize();
 188  3
         }
 189  
 }