«1. Обзор

AsyncHttpClient (AHC) — это библиотека, построенная поверх Netty, с целью простого выполнения HTTP-запросов и асинхронной обработки ответов.

В этой статье мы расскажем, как настроить и использовать HTTP-клиент, как выполнить запрос и обработать ответ с помощью AHC.

2. Установка

Последнюю версию библиотеки можно найти в репозитории Maven. Мы должны быть осторожны при использовании зависимости с идентификатором группы org.asynchttpclient, а не с com.ning: класс ДСЛ. Статический метод asyncHttpClient() возвращает объект AsyncHttpClient:

<dependency>
    <groupId>org.asynchttpclient</groupId>
    <artifactId>async-http-client</artifactId>
    <version>2.2.0</version>
</dependency>

Если нам нужна пользовательская конфигурация HTTP-клиента, мы можем создать объект AsyncHttpClient с помощью компоновщика DefaultAsyncHttpClientConfig.Builder: возможность настроить тайм-ауты, прокси-сервер, HTTP-сертификаты и многое другое:

После того, как мы настроили и получили экземпляр HTTP-клиента, мы можем повторно использовать его во всем приложении. Нам не нужно создавать экземпляр для каждого запроса, потому что внутри он создает новые потоки и пулы соединений, что приведет к проблемам с производительностью.

AsyncHttpClient client = Dsl.asyncHttpClient();

Кроме того, важно отметить, что после того, как мы закончили работу с клиентом, мы должны вызвать метод close(), чтобы предотвратить утечку памяти или зависание ресурсов.

DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config()

4. Создание HTTP-запроса

DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config()
  .setConnectTimeout(500)
  .setProxyServer(new ProxyServer(...));
AsyncHttpClient client = Dsl.asyncHttpClient(clientBuilder);

Существует два метода определения HTTP-запроса с помощью AHC:

связанный несвязанный

Между этими двумя типами запросов нет большой разницы с точки зрения производительности. Они представляют только два отдельных API, которые мы можем использовать для определения запроса. Связанный запрос привязан к HTTP-клиенту, из которого он был создан, и по умолчанию будет использовать конфигурацию этого конкретного клиента, если не указано иное.

Например, при создании связанного запроса флаг disableUrlEncoding считывается из конфигурации HTTP-клиента, а для несвязанного запроса по умолчанию устанавливается в false. Это полезно, поскольку конфигурацию клиента можно изменить без перекомпиляции всего приложения, используя системные свойства, передаваемые в качестве аргументов виртуальной машины:

    Полный список свойств можно найти в файле ahc-default.properties.

4.1. Связанный запрос

Для создания связанного запроса мы используем вспомогательные методы класса AsyncHttpClient, которые начинаются с префикса «prepare». Также мы можем использовать метод prepareRequest(), который получает уже созданный объект Request.

java -jar -Dorg.asynchttpclient.disableUrlEncodingForBoundRequests=true

Например, метод prepareGet() создаст запрос HTTP GET:

4.2. Несвязанный запрос

Несвязанный запрос можно создать с помощью класса RequestBuilder:

или с помощью вспомогательного класса Dsl, который фактически использует RequestBuilder для настройки метода HTTP и URL-адреса запроса:

BoundRequestBuilder getRequest = client.prepareGet("http://www.baeldung.com");

~ ~~ 5. Выполнение HTTP-запросов

Название библиотеки дает нам подсказку о том, как могут выполняться запросы. AHC поддерживает как синхронные, так и асинхронные запросы.

Request getRequest = new RequestBuilder(HttpConstants.Methods.GET)
  .setUrl("http://www.baeldung.com")
  .build();

Выполнение запроса зависит от его типа. При использовании связанного запроса мы используем метод execute() из класса BoundRequestBuilder, а когда у нас есть несвязанный запрос, мы выполним его, используя одну из реализаций метода executeRequest() из интерфейса AsyncHttpClient.

Request getRequest = Dsl.get("http://www.baeldung.com").build()

5.1. Синхронно

Библиотека была спроектирована как асинхронная, но при необходимости мы можем имитировать синхронные вызовы, блокируя объект Future. Оба метода execute() и executeRequest() возвращают объект ListenableFuture\u003cResponse\u003e. Этот класс расширяет интерфейс Java Future, наследуя, таким образом, метод get(), который можно использовать для блокировки текущего потока до тех пор, пока HTTP-запрос не будет завершен и не будет возвращен ответ:

Использование синхронных вызовов полезно при попытке отладки частей нашего кода, но не рекомендуется использовать его в производственной среде, где асинхронное выполнение приводит к повышению производительности и пропускной способности.

5.2. Асинхронно

Future<Response> responseFuture = boundGetRequest.execute();
responseFuture.get();
Future<Response> responseFuture = client.executeRequest(unboundRequest);
responseFuture.get();

«Когда мы говорим об асинхронном выполнении, мы также говорим о слушателях для обработки результатов. Библиотека AHC предоставляет 3 типа прослушивателей, которые можно использовать для асинхронных HTTP-вызовов:

AsyncHandler AsyncCompletionHandler ListenableFuture прослушиватели

AsyncHandler прослушиватель предлагает возможность контролировать и обрабатывать HTTP-вызов до его завершения. С его помощью можно обрабатывать ряд событий, связанных с HTTP-вызовом:

    Перечисление State позволяет нам управлять обработкой HTTP-запроса. Возвращая State.ABORT, мы можем остановить обработку в определенный момент, а используя State.CONTINUE, мы позволяем обработке завершиться.

Важно отметить, что AsyncHandler не является потокобезопасным и не должен повторно использоваться при выполнении параллельных запросов.

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;
    }
});

AsyncCompletionHandler наследует все методы интерфейса AsyncHandler и добавляет вспомогательный метод onCompleted(Response) для обработки завершения вызова. Все остальные методы прослушивателя переопределены для возврата State.CONTINUE, что делает код более читаемым:

Интерфейс ListenableFuture позволяет нам добавлять прослушиватели, которые будут запускаться после завершения HTTP-вызова.

Кроме того, он позволяет выполнять код из слушателей, используя другой пул потоков:

request.execute(new AsyncCompletionHandler<Object>() {
    @Override
    public Object onCompleted(Response response) throws Exception {
        return response;
    }
});

Кроме того, возможность добавлять слушателей, интерфейс ListenableFuture позволяет нам преобразовать ответ Future в CompletableFuture.

7. Заключение

ListenableFuture<Response> listenableFuture = client
  .executeRequest(unboundRequest);
listenableFuture.addListener(() -> {
    Response response = listenableFuture.get();
    LOG.debug(response.getStatusCode());
}, Executors.newCachedThreadPool());

AHC — очень мощная библиотека с множеством интересных функций. Он предлагает очень простой способ настройки HTTP-клиента и возможность выполнения как синхронных, так и асинхронных запросов.

Как всегда, исходный код статьи доступен на GitHub.

«

As always, the source code for the article is available over on GitHub.