com.ning.http.client.AsyncHttpClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of async-http-client Show documentation
Show all versions of async-http-client Show documentation
Async Http Client library purpose is to allow Java applications to easily execute HTTP requests and
asynchronously process the HTTP responses.
The newest version!
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you 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.
*
*/
package com.ning.http.client;
import java.io.Closeable;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ning.http.client.cookie.Cookie;
import com.ning.http.client.filter.FilterContext;
import com.ning.http.client.filter.FilterException;
import com.ning.http.client.filter.RequestFilter;
import com.ning.http.client.multipart.Part;
import com.ning.http.client.providers.jdk.JDKAsyncHttpProvider;
import com.ning.http.client.resumable.ResumableAsyncHandler;
/**
* This class support asynchronous and synchronous HTTP request.
*
* To execute synchronous HTTP request, you just need to do
*
* AsyncHttpClient c = new AsyncHttpClient();
* Future f = c.prepareGet("http://www.ning.com/").execute();
*
* The code above will block until the response is fully received. To execute asynchronous HTTP request, you
* create an {@link AsyncHandler} or its abstract implementation, {@link com.ning.http.client.AsyncCompletionHandler}
*
*
* AsyncHttpClient c = new AsyncHttpClient();
* Future f = c.prepareGet("http://www.ning.com/").execute(new AsyncCompletionHandler() {
*
* @Override
* public Response onCompleted(Response response) throws IOException {
* // Do something
* return response;
* }
*
* @Override
* public void onThrowable(Throwable t) {
* }
* });
* Response response = f.get();
*
* // We are just interested to retrieve the status code.
* Future f = c.prepareGet("http://www.ning.com/").execute(new AsyncCompletionHandler() {
*
* @Override
* public Integer onCompleted(Response response) throws IOException {
* // Do something
* return response.getStatusCode();
* }
*
* @Override
* public void onThrowable(Throwable t) {
* }
* });
* Integer statusCode = f.get();
*
* You can also have more control about the how the response is asynchronously processed by using a {@link AsyncHandler}
*
* AsyncHttpClient c = new AsyncHttpClient();
* Future f = c.prepareGet("http://www.ning.com/").execute(new AsyncHandler() {
* private StringBuilder builder = new StringBuilder();
*
* @Override
* public STATE onStatusReceived(HttpResponseStatus s) throws Exception {
* // return STATE.CONTINUE or STATE.ABORT
* return STATE.CONTINUE
* }
*
* @Override
* public STATE onHeadersReceived(HttpResponseHeaders bodyPart) throws Exception {
* // return STATE.CONTINUE or STATE.ABORT
* return STATE.CONTINUE
*
* }
* @Override
*
* public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception {
* builder.append(new String(bodyPart));
* // return STATE.CONTINUE or STATE.ABORT
* return STATE.CONTINUE
* }
*
* @Override
* public String onCompleted() throws Exception {
* // Will be invoked once the response has been fully read or a ResponseComplete exception
* // has been thrown.
* return builder.toString();
* }
*
* @Override
* public void onThrowable(Throwable t) {
* }
* });
*
* String bodyResponse = f.get();
*
* This class can also be used without the need of {@link AsyncHandler}
*
* AsyncHttpClient c = new AsyncHttpClient();
* Future f = c.prepareGet(TARGET_URL).execute();
* Response r = f.get();
*
*
* Finally, you can configure the AsyncHttpClient using an {@link AsyncHttpClientConfig} instance
*
* AsyncHttpClient c = new AsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(...).build());
* Future f = c.prepareGet(TARGET_URL).execute();
* Response r = f.get();
*
*
* An instance of this class will cache every HTTP 1.1 connections and close them when the {@link AsyncHttpClientConfig#getReadTimeout()}
* expires. This object can hold many persistent connections to different host.
*/
public class AsyncHttpClient implements Closeable {
private final static String DEFAULT_PROVIDER = "com.ning.http.client.providers.netty.NettyAsyncHttpProvider";
private final AsyncHttpProvider httpProvider;
private final AsyncHttpClientConfig config;
private final static Logger logger = LoggerFactory.getLogger(AsyncHttpClient.class);
private final AtomicBoolean isClosed = new AtomicBoolean(false);
/**
* Default signature calculator to use for all requests constructed by this client instance.
*
* @since 1.1
*/
protected SignatureCalculator signatureCalculator;
/**
* Create a new HTTP Asynchronous Client using the default {@link AsyncHttpClientConfig} configuration. The
* default {@link AsyncHttpProvider} will be used ({@link com.ning.http.client.providers.netty.NettyAsyncHttpProvider}
*/
public AsyncHttpClient() {
this(new AsyncHttpClientConfig.Builder().build());
}
/**
* Create a new HTTP Asynchronous Client using an implementation of {@link AsyncHttpProvider} and
* the default {@link AsyncHttpClientConfig} configuration.
*
* @param provider a {@link AsyncHttpProvider}
*/
public AsyncHttpClient(AsyncHttpProvider provider) {
this(provider, new AsyncHttpClientConfig.Builder().build());
}
/**
* Create a new HTTP Asynchronous Client using a {@link AsyncHttpClientConfig} configuration and the
* {@link #DEFAULT_PROVIDER}
*
* @param config a {@link AsyncHttpClientConfig}
*/
public AsyncHttpClient(AsyncHttpClientConfig config) {
this(loadDefaultProvider(DEFAULT_PROVIDER, config), config);
}
/**
* Create a new HTTP Asynchronous Client using a {@link AsyncHttpClientConfig} configuration and
* and a {@link AsyncHttpProvider}.
*
* @param config a {@link AsyncHttpClientConfig}
* @param httpProvider a {@link AsyncHttpProvider}
*/
public AsyncHttpClient(AsyncHttpProvider httpProvider, AsyncHttpClientConfig config) {
this.config = config;
this.httpProvider = httpProvider;
}
/**
* Create a new HTTP Asynchronous Client using a {@link AsyncHttpClientConfig} configuration and
* and a AsyncHttpProvider class' name.
*
* @param config a {@link AsyncHttpClientConfig}
* @param providerClass a {@link AsyncHttpProvider}
*/
public AsyncHttpClient(String providerClass, AsyncHttpClientConfig config) {
this.config = new AsyncHttpClientConfig.Builder().build();
this.httpProvider = loadDefaultProvider(providerClass, config);
}
public class BoundRequestBuilder extends RequestBuilderBase {
private BoundRequestBuilder(String method, boolean isDisableUrlEncoding) {
super(BoundRequestBuilder.class, method, isDisableUrlEncoding);
}
private BoundRequestBuilder(Request prototype) {
super(BoundRequestBuilder.class, prototype);
}
public ListenableFuture execute(AsyncHandler handler) {
return AsyncHttpClient.this.executeRequest(build(), handler);
}
public ListenableFuture execute() {
return AsyncHttpClient.this.executeRequest(build(), new AsyncCompletionHandlerBase());
}
// Note: For now we keep the delegates in place even though they are not needed
// since otherwise Clojure (and maybe other languages) won't be able to
// access these methods - see Clojure tickets 126 and 259
@Override
public BoundRequestBuilder addBodyPart(Part part) {
return super.addBodyPart(part);
}
@Override
public BoundRequestBuilder addCookie(Cookie cookie) {
return super.addCookie(cookie);
}
@Override
public BoundRequestBuilder addHeader(String name, String value) {
return super.addHeader(name, value);
}
@Override
public BoundRequestBuilder addFormParam(String key, String value) {
return super.addFormParam(key, value);
}
@Override
public BoundRequestBuilder addQueryParam(String name, String value) {
return super.addQueryParam(name, value);
}
@Override
public Request build() {
return super.build();
}
@Override
public BoundRequestBuilder setBody(byte[] data) {
return super.setBody(data);
}
@Override
public BoundRequestBuilder setBody(InputStream stream) {
return super.setBody(stream);
}
@Override
public BoundRequestBuilder setBody(String data) {
return super.setBody(data);
}
@Override
public BoundRequestBuilder setHeader(String name, String value) {
return super.setHeader(name, value);
}
@Override
public BoundRequestBuilder setHeaders(FluentCaseInsensitiveStringsMap headers) {
return super.setHeaders(headers);
}
@Override
public BoundRequestBuilder setHeaders(Map> headers) {
return super.setHeaders(headers);
}
@Override
public BoundRequestBuilder setFormParams(Map> params) {
return super.setFormParams(params);
}
@Override
public BoundRequestBuilder setFormParams(List params) {
return super.setFormParams(params);
}
@Override
public BoundRequestBuilder setUrl(String url) {
return super.setUrl(url);
}
@Override
public BoundRequestBuilder setVirtualHost(String virtualHost) {
return super.setVirtualHost(virtualHost);
}
public BoundRequestBuilder setSignatureCalculator(SignatureCalculator signatureCalculator) {
return super.setSignatureCalculator(signatureCalculator);
}
}
/**
* Return the asynchronous {@link com.ning.http.client.AsyncHttpProvider}
*
* @return an {@link com.ning.http.client.AsyncHttpProvider}
*/
public AsyncHttpProvider getProvider() {
return httpProvider;
}
/**
* Close the underlying connections.
*/
public void close() {
if (isClosed.compareAndSet(false, true)) {
httpProvider.close();
}
}
/**
* Asynchronous close the {@link AsyncHttpProvider} by spawning a thread and avoid blocking.
*/
public void closeAsynchronously() {
final ExecutorService e = Executors.newSingleThreadExecutor();
e.submit(new Runnable() {
public void run() {
try {
close();
} catch (Throwable t) {
logger.warn("", t);
} finally {
e.shutdown();
}
}
});
}
@Override
protected void finalize() throws Throwable {
try {
if (!isClosed.get()) {
logger.debug("AsyncHttpClient.close() hasn't been invoked, which may produce file descriptor leaks");
}
} finally {
super.finalize();
}
}
/**
* Return true if closed
*
* @return true if closed
*/
public boolean isClosed() {
return isClosed.get();
}
/**
* Return the {@link com.ning.http.client.AsyncHttpClientConfig}
*
* @return {@link com.ning.http.client.AsyncHttpClientConfig}
*/
public AsyncHttpClientConfig getConfig() {
return config;
}
/**
* Set default signature calculator to use for requests build by this client instance
*/
public AsyncHttpClient setSignatureCalculator(SignatureCalculator signatureCalculator) {
this.signatureCalculator = signatureCalculator;
return this;
}
/**
* Prepare an HTTP client GET request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder prepareGet(String url) {
return requestBuilder("GET", url);
}
/**
* Prepare an HTTP client CONNECT request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder prepareConnect(String url) {
return requestBuilder("CONNECT", url);
}
/**
* Prepare an HTTP client OPTIONS request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder prepareOptions(String url) {
return requestBuilder("OPTIONS", url);
}
/**
* Prepare an HTTP client HEAD request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder prepareHead(String url) {
return requestBuilder("HEAD", url);
}
/**
* Prepare an HTTP client POST request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder preparePost(String url) {
return requestBuilder("POST", url);
}
/**
* Prepare an HTTP client PUT request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder preparePut(String url) {
return requestBuilder("PUT", url);
}
/**
* Prepare an HTTP client DELETE request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder prepareDelete(String url) {
return requestBuilder("DELETE", url);
}
/**
* Prepare an HTTP client PATCH request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder preparePatch(String url) {
return requestBuilder("PATCH", url);
}
/**
* Prepare an HTTP client TRACE request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder prepareTrace(String url) {
return requestBuilder("TRACE", url);
}
/**
* Construct a {@link RequestBuilder} using a {@link Request}
*
* @param request a {@link Request}
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder prepareRequest(Request request) {
return requestBuilder(request);
}
/**
* Execute an HTTP request.
*
* @param request {@link Request}
* @param handler an instance of {@link AsyncHandler}
* @param Type of the value that will be returned by the associated {@link java.util.concurrent.Future}
* @return a {@link Future} of type T
*/
public ListenableFuture executeRequest(Request request, AsyncHandler handler) {
if (config.getRequestFilters().isEmpty()) {
return httpProvider.execute(request, handler);
} else {
FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(handler).request(request).build();
try {
fc = preProcessRequest(fc);
} catch (Exception e) {
handler.onThrowable(e);
return new ListenableFuture.CompletedFailure("preProcessRequest failed", e);
}
return httpProvider.execute(fc.getRequest(), fc.getAsyncHandler());
}
}
/**
* Execute an HTTP request.
*
* @param request {@link Request}
* @return a {@link Future} of type Response
*/
public ListenableFuture executeRequest(Request request) {
return executeRequest(request, new AsyncCompletionHandlerBase());
}
/**
* Configure and execute the associated {@link RequestFilter}. This class may decorate the {@link Request} and {@link AsyncHandler}
*
* @param fc {@link FilterContext}
* @return {@link FilterContext}
*/
private FilterContext preProcessRequest(FilterContext fc) throws FilterException {
for (RequestFilter asyncFilter : config.getRequestFilters()) {
fc = asyncFilter.filter(fc);
if (fc == null) {
throw new NullPointerException("FilterContext is null");
}
}
Request request = fc.getRequest();
if (fc.getAsyncHandler() instanceof ResumableAsyncHandler) {
request = ResumableAsyncHandler.class.cast(fc.getAsyncHandler()).adjustRequestRange(request);
}
if (request.getRangeOffset() != 0) {
RequestBuilder builder = new RequestBuilder(request);
builder.setHeader("Range", "bytes=" + request.getRangeOffset() + "-");
request = builder.build();
}
fc = new FilterContext.FilterContextBuilder(fc).request(request).build();
return fc;
}
@SuppressWarnings("unchecked")
private final static AsyncHttpProvider loadDefaultProvider(String className, AsyncHttpClientConfig config) {
try {
Class providerClass = (Class) Thread.currentThread()
.getContextClassLoader().loadClass(className);
return providerClass.getDeclaredConstructor(
new Class[]{AsyncHttpClientConfig.class}).newInstance(new Object[]{config});
} catch (Throwable t) {
if (t instanceof InvocationTargetException) {
final InvocationTargetException ite = (InvocationTargetException) t;
if (logger.isErrorEnabled()) {
logger.error(
"Unable to instantiate provider {}. Trying other providers.",
className);
logger.error(ite.getCause().toString(), ite.getCause());
}
}
// Let's try with another classloader
try {
Class providerClass = (Class)
AsyncHttpClient.class.getClassLoader().loadClass(className);
return providerClass.getDeclaredConstructor(
new Class[]{AsyncHttpClientConfig.class}).newInstance(new Object[]{config});
} catch (Throwable t2) {
}
if (logger.isDebugEnabled()) {
logger.debug("Default provider not found {}. Using the {}", DEFAULT_PROVIDER,
JDKAsyncHttpProvider.class.getName());
}
return new JDKAsyncHttpProvider(config);
}
}
protected BoundRequestBuilder requestBuilder(String method, String url) {
return new BoundRequestBuilder(method, config.isDisableUrlEncodingForBoundedRequests()).setUrl(url).setSignatureCalculator(signatureCalculator);
}
protected BoundRequestBuilder requestBuilder(Request prototype) {
return new BoundRequestBuilder(prototype).setSignatureCalculator(signatureCalculator);
}
}