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 }