All Downloads are FREE. Search and download functionalities are using the official Maven repository.

groovyx.net.http.AsyncHTTPBuilder Maven / Gradle / Ivy

Go to download

A builder-style HTTP client API, including authentication, and extensible handling of common content-types such as JSON and XML. It is built on top of Apache's HttpClient.

The newest version!
/*
 * 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 get
 * and post) 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 Tom Nichols
 */
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:
     * 
*
threadPool
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.
*
poolSize
Max number of concurrent requests
*
uri
Default request URI
*
contentType
Default content type for requests and responses
*
timeout
Timeout in milliseconds to wait for a connection to * be established and request to complete.
*
*/ public AsyncHTTPBuilder( Map args ) throws URISyntaxException { 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 request methods * (including get and post) 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() { /*@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 = super.getClient().getParams(); 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 ); setClient(new DefaultHttpClient( cm, params )); this.threadPool = threadPool != null ? threadPool : new ThreadPoolExecutor( poolSize, poolSize, 120, TimeUnit.SECONDS, new LinkedBlockingQueue() ); } /** * {@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 ex.getCause(). * */ @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() ); } /** *

Access the underlying threadpool to adjust things like job timeouts.

* *

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 attempted which will then simply * block while waiting for a free connection.

* * @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(); } }