1. Overview
AsyncHttpClient (AHC) is a library build on top of Netty, with the purpose of easily executing HTTP requests and processing responses asynchronously.
In this article, we’ll present how to configure and use the HTTP client, how to execute a request and process the response using AHC.
2. Setup
The latest version of the library can be found in the Maven repository. We should be careful to use the dependency with the group id org.asynchttpclient and not the one with com.ning:
<dependency>
<groupId>org.asynchttpclient</groupId>
<artifactId>async-http-client</artifactId>
<version>2.2.0</version>
</dependency>
3. HTTP Client Configuration
The most straightforward method of obtaining the HTTP client is by using the Dsl class. The static asyncHttpClient() method returns an AsyncHttpClient object:
AsyncHttpClient client = Dsl.asyncHttpClient();
If we need a custom configuration of the HTTP client, we can build the AsyncHttpClient object using the builder DefaultAsyncHttpClientConfig.Builder:
DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config()
This offers the possibility to configure timeouts, a proxy server, HTTP certificates and many more:
DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config()
.setConnectTimeout(500)
.setProxyServer(new ProxyServer(...));
AsyncHttpClient client = Dsl.asyncHttpClient(clientBuilder);
Once we’ve configured and obtained an instance of the HTTP client we can reuse it across out application. We don’t need to create an instance for each request because internally it creates new threads and connection pools, which will lead to performance issues.
Also, it’s important to note that once we’ve finished using the client we should call to close() method to prevent any memory leaks or hanging resources.
4. Creating an HTTP Request
There are two methods in which we can define an HTTP request using AHC:
- bound
- unbound
There is no major difference between the two request types in terms of performance. They only represent two separate APIs we can use to define a request. A bound request is tied to the HTTP client it was created from and will, by default, use the configuration of that specific client if not specified otherwise.
For example, when creating a bound request the disableUrlEncoding flag is read from the HTTP client configuration, while for an unbound request this is, by default set to false. This is useful because the client configuration can be changed without recompiling the whole application by using system properties passed as VM arguments:
java -jar -Dorg.asynchttpclient.disableUrlEncodingForBoundRequests=true
A complete list of properties can be found the ahc-default.properties file.
4.1. Bound Request
To create a bound request we use the helper methods from the class AsyncHttpClient that start with the prefix “prepare”. Also, we can use the prepareRequest() method which receives an already created Request object.
For example, the prepareGet() method will create an HTTP GET request:
BoundRequestBuilder getRequest = client.prepareGet("http://www.baeldung.com");
4.2. Unbound Request
An unbound request can be created using the RequestBuilder class:
Request getRequest = new RequestBuilder(HttpConstants.Methods.GET)
.setUrl("http://www.baeldung.com")
.build();
or by using the Dsl helper class, which actually uses the RequestBuilder for configuring the HTTP method and URL of the request:
Request getRequest = Dsl.get("http://www.baeldung.com").build()
5. Executing HTTP Requests
The name of the library gives us a hint about how the requests can be executed. AHC has support for both synchronous and asynchronous requests.
Executing the request depends on its type. When using a bound request we use the execute() method from the BoundRequestBuilder class and when we have an unbound request we’ll execute it using one of the implementations of the executeRequest() method from the AsyncHttpClient interface.
5.1. Synchronously
The library was designed to be asynchronous, but when needed we can simulate synchronous calls by blocking on the Future object. Both execute() and executeRequest() methods return a ListenableFuture
Future<Response> responseFuture = boundGetRequest.execute();
responseFuture.get();
Future<Response> responseFuture = client.executeRequest(unboundRequest);
responseFuture.get();
Using synchronous calls is useful when trying to debug parts of our code, but it’s not recommended to be used in a production environment where asynchronous executions lead to better performance and throughput.
5.2. Asynchronously
When we talk about asynchronous executions, we also talk about listeners for processing the results. The AHC library provides 3 types of listeners that can be used for asynchronous HTTP calls:
- AsyncHandler
- AsyncCompletionHandler
- ListenableFuture listeners
The AsyncHandler listener offers the possibility to control and process the HTTP call before it has completed. Using it can handle a series of events related to the HTTP call:
request.execute(new AsyncHandler<Object>() {
@Override
public State onStatusReceived(HttpResponseStatus responseStatus)
throws Exception {
return null;
}
@Override
public State onHeadersReceived(HttpHeaders headers)
throws Exception {
return null;
}
@Override
public State onBodyPartReceived(HttpResponseBodyPart bodyPart)
throws Exception {
return null;
}
@Override
public void onThrowable(Throwable t) {
}
@Override
public Object onCompleted() throws Exception {
return null;
}
});
The State enum lets us control the processing of the HTTP request. By returning State.ABORT we can stop the processing at a specific moment and by using State.CONTINUE we let the processing finish.
It’s important to mention that the AsyncHandler isn’t thread-safe and shouldn’t be reused when executing concurrent requests.
AsyncCompletionHandler inherits all the methods from the AsyncHandler interface and adds the onCompleted(Response) helper method for handling the call completion. All the other listener methods are overridden to return *State.*CONTINUE, thus making the code more readable:
request.execute(new AsyncCompletionHandler<Object>() {
@Override
public Object onCompleted(Response response) throws Exception {
return response;
}
});
The ListenableFuture interface lets us add listeners that will run when the HTTP call is completed.
Also, it let’s execute the code from the listeners – by using another thread pool:
ListenableFuture<Response> listenableFuture = client
.executeRequest(unboundRequest);
listenableFuture.addListener(() -> {
Response response = listenableFuture.get();
LOG.debug(response.getStatusCode());
}, Executors.newCachedThreadPool());
Besides, the option to add listeners, the ListenableFuture interface lets us transform the Future response to a CompletableFuture.
7. Conclusion
AHC is a very powerful library, with a lot of interesting features. It offers a very simple way to configure an HTTP client and the capability of executing both synchronous and asynchronous requests.
As always, the source code for the article is available over on GitHub.