View Javadoc

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  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  	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  		super();
76  		Object poolSize = args.get("poolSize");
77  		if ( poolSize == null ) poolSize = DEFAULT_POOL_SIZE;
78  		this.initThreadPools( (Integer)poolSize );
79  		
80  		Object defaultURL = args.get("url");
81  		if ( defaultURL != null ) super.setURL(defaultURL);
82  
83  		Object defaultContentType = args.get("contentType");
84  		if ( defaultContentType != null ) 
85  			super.setContentType(defaultContentType);
86  	}
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  		return threadPool.submit( new Callable<Object>() {
100 			/*@Override*/ public Object call() throws Exception {
101 				try {
102 					return doRequestSuper(delegate);
103 				}
104 				catch( Exception ex ) {
105 					log.error( "Exception thrown from request delegate: " + 
106 							delegate, ex );
107 					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 		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 		if (poolSize < 1) throw new IllegalArgumentException("poolSize may not be < 1");
127 		// Create and initialize HTTP parameters
128 		HttpParams params = client != null ? client.getParams()
129 				: new BasicHttpParams();
130 		ConnManagerParams.setMaxTotalConnections(params, poolSize);
131 		ConnManagerParams.setMaxConnectionsPerRoute(params,
132 				new ConnPerRouteBean(poolSize));
133 
134 		HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
135 
136 		// Create and initialize scheme registry
137 		SchemeRegistry schemeRegistry = new SchemeRegistry();
138 		schemeRegistry.register( new Scheme( "http", 
139 				PlainSocketFactory.getSocketFactory(), 80 ) );
140 		schemeRegistry.register( new Scheme( "https", 
141 				SSLSocketFactory.getSocketFactory(), 443));
142 
143 		ClientConnectionManager cm = new ThreadSafeClientConnManager(
144 				params, schemeRegistry );
145 		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 		this.threadPool.setMaximumPoolSize(poolSize);
153 	}
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 		return this.threadPool;
171 	}
172 	
173 	/**
174 	 * {@inheritDoc}
175 	 */
176 	@Override public void shutdown() {
177 		super.shutdown(); 
178 		this.threadPool.shutdown();
179 	}
180 	
181 	/**
182 	 * {@inheritDoc}
183 	 * @see #shutdown()
184 	 */
185 	@Override protected void finalize() throws Throwable {
186 		this.shutdown();
187 		super.finalize();
188 	}
189 }