001 /*
002 * Copyright 2003-2008 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * You are receiving this code free of charge, which represents many hours of
017 * effort from other individuals and corporations. As a responsible member
018 * of the community, you are asked (but not required) to donate any
019 * enhancements or improvements back to the community under a similar open
020 * source license. Thank you. -TMN
021 */
022 package groovyx.net.http;
023
024 import java.io.IOException;
025 import java.net.URISyntaxException;
026 import java.util.Map;
027 import java.util.concurrent.Callable;
028 import java.util.concurrent.Executors;
029 import java.util.concurrent.Future;
030 import java.util.concurrent.ThreadPoolExecutor;
031
032 import org.apache.http.HttpVersion;
033 import org.apache.http.client.ClientProtocolException;
034 import org.apache.http.conn.ClientConnectionManager;
035 import org.apache.http.conn.params.ConnManagerParams;
036 import org.apache.http.conn.params.ConnPerRouteBean;
037 import org.apache.http.conn.scheme.PlainSocketFactory;
038 import org.apache.http.conn.scheme.Scheme;
039 import org.apache.http.conn.scheme.SchemeRegistry;
040 import org.apache.http.conn.ssl.SSLSocketFactory;
041 import org.apache.http.impl.client.DefaultHttpClient;
042 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
043 import org.apache.http.params.BasicHttpParams;
044 import org.apache.http.params.HttpParams;
045 import org.apache.http.params.HttpProtocolParams;
046
047 /**
048 * This implementation makes all requests asynchronous by submitting jobs to a
049 * {@link ThreadPoolExecutor}. All request methods (including <code>get</code>
050 * and <code>post</code>) return a {@link Future} instance, whose
051 * {@link Future#get() get} method will provide access to whatever value was
052 * returned from the response handler closure.
053 *
054 * @author <a href='mailto:tomstrummer+httpbuilder@gmail.com'>Tom Nichols</a>
055 */
056 public class AsyncHTTPBuilder extends HTTPBuilder {
057
058 /**
059 * Default pool size is one is not supplied in the constructor.
060 */
061 public static final int DEFAULT_POOL_SIZE = 4;
062
063 protected final ThreadPoolExecutor threadPool =
064 (ThreadPoolExecutor)Executors.newCachedThreadPool();
065
066 /**
067 * Accepts the following named parameters:
068 * <dl>
069 * <dt>poolSize</dt><dd>Max number of concurrent requests</dd>
070 * <dt>url</dt><dd>Default request URL</dd>
071 * <dt>contentType</dt><dd>Default content type for requests and responses</dd>
072 * </dl>
073 */
074 public AsyncHTTPBuilder( Map<String, ?> args ) throws URISyntaxException {
075 super();
076 Object poolSize = args.get("poolSize");
077 if ( poolSize == null ) poolSize = DEFAULT_POOL_SIZE;
078 this.initThreadPools( (Integer)poolSize );
079
080 Object defaultURL = args.get("url");
081 if ( defaultURL != null ) super.setURL(defaultURL);
082
083 Object defaultContentType = args.get("contentType");
084 if ( defaultContentType != null )
085 super.setContentType(defaultContentType);
086 }
087
088 /**
089 * Submits a {@link Callable} instance to the job pool, which in turn will
090 * call {@link HTTPBuilder#doRequest(SendDelegate)} in an asynchronous
091 * thread. The {@link Future} instance returned by this value (which in
092 * turn should be returned by any of the public <code>request</code> methods
093 * (including <code>get</code> and <code>post</code>) may be used to
094 * retrieve whatever value may be returned from the executed response
095 * handler closure.
096 */
097 @Override
098 protected Future<?> doRequest( final SendDelegate delegate ) {
099 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 }