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 }