Java tutorial
/* * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * You are receiving this code free of charge, which represents many hours of * effort from other individuals and corporations. As a responsible member * of the community, you are encouraged (but not required) to donate any * enhancements or improvements back to the community under a similar open * source license. Thank you. -TMN */ package groovyx.net.http; import java.io.IOException; import java.net.URISyntaxException; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.http.HttpVersion; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.params.ConnManagerParams; import org.apache.http.conn.params.ConnPerRouteBean; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; /** * This implementation makes all requests asynchronous by submitting jobs to a * {@link ThreadPoolExecutor}. All request methods (including <code>get</code> * and <code>post</code>) return a {@link Future} instance, whose * {@link Future#get() get} method will provide access to whatever value was * returned from the response handler closure. * * @author <a href='mailto:tomstrummer+httpbuilder@gmail.com'>Tom Nichols</a> */ public class AsyncHTTPBuilder extends HTTPBuilder { /** * Default pool size is one is not supplied in the constructor. */ public static final int DEFAULT_POOL_SIZE = 4; protected ExecutorService threadPool; // = (ThreadPoolExecutor)Executors.newCachedThreadPool(); /** * Accepts the following named parameters: * <dl> * <dt>threadPool</dt><dd>Custom {@link ExecutorService} instance for * running submitted requests. If this is an instance of {@link ThreadPoolExecutor}, * the poolSize will be determined by {@link ThreadPoolExecutor#getMaximumPoolSize()}. * The default threadPool uses an unbounded queue to accept an unlimited * number of requests.</dd> * <dt>poolSize</dt><dd>Max number of concurrent requests</dd> * <dt>uri</dt><dd>Default request URI</dd> * <dt>contentType</dt><dd>Default content type for requests and responses</dd> * <dt>timeout</dt><dd>Timeout in milliseconds to wait for a connection to * be established and request to complete.</dd> * </dl> */ public AsyncHTTPBuilder(Map<String, ?> args) throws URISyntaxException { super(); int poolSize = DEFAULT_POOL_SIZE; ExecutorService threadPool = null; if (args != null) { threadPool = (ExecutorService) args.remove("threadPool"); if (threadPool instanceof ThreadPoolExecutor) poolSize = ((ThreadPoolExecutor) threadPool).getMaximumPoolSize(); Object poolSzArg = args.remove("poolSize"); if (poolSzArg != null) poolSize = Integer.parseInt(poolSzArg.toString()); if (args.containsKey("url")) throw new IllegalArgumentException("The 'url' parameter is deprecated; use 'uri' instead"); Object defaultURI = args.remove("uri"); if (defaultURI != null) super.setUri(defaultURI); Object defaultContentType = args.remove("contentType"); if (defaultContentType != null) super.setContentType(defaultContentType); Object timeout = args.remove("timeout"); if (timeout != null) setTimeout((Integer) timeout); if (args.size() > 0) { String invalidArgs = ""; for (String k : args.keySet()) invalidArgs += k + ","; throw new IllegalArgumentException("Unexpected keyword args: " + invalidArgs); } } this.initThreadPools(poolSize, threadPool); } /** * Submits a {@link Callable} instance to the job pool, which in turn will * call {@link HTTPBuilder#doRequest(RequestConfigDelegate)} in an asynchronous * thread. The {@link Future} instance returned by this value (which in * turn should be returned by any of the public <code>request</code> methods * (including <code>get</code> and <code>post</code>) may be used to * retrieve whatever value may be returned from the executed response * handler closure. */ @Override protected Future<?> doRequest(final RequestConfigDelegate delegate) { return threadPool.submit(new Callable<Object>() { /*@Override*/ public Object call() throws Exception { try { return doRequestSuper(delegate); } catch (Exception ex) { log.info("Exception thrown from response delegate: " + delegate, ex); throw ex; } } }); } /* * Because we can't call "super.doRequest" from within the anonymous * Callable subclass. */ private Object doRequestSuper(RequestConfigDelegate delegate) throws IOException { return super.doRequest(delegate); } /** * Initializes threading parameters for the HTTPClient's * {@link ThreadSafeClientConnManager}, and this class' ThreadPoolExecutor. */ protected void initThreadPools(final int poolSize, final ExecutorService threadPool) { if (poolSize < 1) throw new IllegalArgumentException("poolSize may not be < 1"); // Create and initialize HTTP parameters HttpParams params = client != null ? client.getParams() : new BasicHttpParams(); ConnManagerParams.setMaxTotalConnections(params, poolSize); ConnManagerParams.setMaxConnectionsPerRoute(params, new ConnPerRouteBean(poolSize)); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); // Create and initialize scheme registry SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry); super.client = new DefaultHttpClient(cm, params); this.threadPool = threadPool != null ? threadPool : new ThreadPoolExecutor(poolSize, poolSize, 120, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); } /** * {@inheritDoc} */ @Override protected Object defaultSuccessHandler(HttpResponseDecorator resp, Object parsedData) throws ResponseParseException { return super.defaultSuccessHandler(resp, parsedData); } /** * For 'failure' responses (e.g. a 404), the exception will be wrapped in * a {@link ExecutionException} and held by the {@link Future} instance. * The exception is then re-thrown when calling {@link Future#get() * future.get()}. You can access the original exception (e.g. an * {@link HttpResponseException}) by calling <code>ex.getCause()</code>. * */ @Override protected void defaultFailureHandler(HttpResponseDecorator resp) throws HttpResponseException { super.defaultFailureHandler(resp); } /** * This timeout is used for both the time to wait for an established * connection, and the time to wait for data. * @see HttpConnectionParams#setSoTimeout(HttpParams, int) * @see HttpConnectionParams#setConnectionTimeout(HttpParams, int) * @param timeout time to wait in milliseconds. */ public void setTimeout(int timeout) { HttpConnectionParams.setConnectionTimeout(super.getClient().getParams(), timeout); HttpConnectionParams.setSoTimeout(super.getClient().getParams(), timeout); /* this will cause a thread waiting for an available connection instance * to time-out */ // ConnManagerParams.setTimeout( super.getClient().getParams(), timeout ); } /** * Get the timeout in for establishing an HTTP connection. * @return timeout in milliseconds. */ public int getTimeout() { return HttpConnectionParams.getConnectionTimeout(super.getClient().getParams()); } /** * <p>Access the underlying threadpool to adjust things like job timeouts.</p> * * <p>Note that this is not the same pool used by the HttpClient's * {@link ThreadSafeClientConnManager}. Therefore, increasing the * {@link ThreadPoolExecutor#setMaximumPoolSize(int) maximum pool size} will * not in turn increase the number of possible concurrent requests. It will * simply cause more requests to be <i>attempted</i> which will then simply * block while waiting for a free connection.</p> * * @return the service used to execute requests. By default this is a * {@link ThreadPoolExecutor}. */ public ExecutorService getThreadExecutor() { return this.threadPool; } /** * {@inheritDoc} */ @Override public void shutdown() { super.shutdown(); this.threadPool.shutdown(); } /** * {@inheritDoc} * @see #shutdown() */ @Override protected void finalize() throws Throwable { this.shutdown(); super.finalize(); } }