Рубрики
Без рубрики

Изучение нового HTTP-клиента на Java

Изучите новый API Java HttpClient, который обеспечивает большую гибкость и мощные функции.

Автор оригинала: baeldung.

1. введение

В этом уроке мы рассмотрим новую инкубацию Java 9 HttpClient .

До недавнего времени Java предоставляла только HttpURLConnection API, который является низкоуровневым и не известен как многофункциональный | и | удобный для пользователя.

Поэтому обычно использовались некоторые широко используемые сторонние библиотеки, такие как Apache HttpClient , Jetty и Spring RestTemplate .

2. Начальная настройка

Модуль HttpClient поставляется в комплекте с модулем инкубатора в JDK 9 и поддерживает HTTP/2 с обратной совместимостью, по-прежнему облегчающей HTTP/1.1.

Чтобы использовать его, нам нужно определить наш модуль с помощью module-info.java файл, который также указывает необходимый модуль для запуска нашего приложения:

module com.baeldung.java9.httpclient {   
  requires jdk.incubator.httpclient;
}

3. Обзор API HTTP-клиента

В отличие от HttpURLConnection, HttpClient обеспечивает синхронные и асинхронные механизмы запросов.

API состоит из 3 основных классов:

  • HttpRequest представляет запрос, который будет отправлен через HttpClient
  • HttpClient ведет себя как контейнер для информации о конфигурации, общей для нескольких запросов
  • HttpResponse представляет результат вызова HttpRequest

Мы рассмотрим каждый из них более подробно в следующих разделах. Во – первых, давайте сосредоточимся на запросе.

4. HttpRequest

Http-запрос, как следует из названия , – это объект, представляющий запрос, который мы хотим отправить. Новые экземпляры могут быть созданы с помощью HttpRequest.Строитель.

Мы можем получить его по телефону HttpRequest.new Builder() . Builder класс предоставляет множество методов, которые мы можем использовать для настройки нашего запроса.

Мы рассмотрим самые важные из них.

4.1. Настройка URI

Первое, что мы должны сделать при создании запроса, – это указать URL-адрес.

Мы можем сделать это двумя способами – используя конструктор для Builder с параметром URI или вызвав метод uri(URI) на экземпляре Builder :

HttpRequest.newBuilder(new URI("https://postman-echo.com/get"))
 
HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))

Последнее, что нам нужно настроить для создания базового запроса, – это метод HTTP.

4.2. Указание метода HTTP

Мы можем определить метод HTTP, который будет использоваться вашим запросом, вызвав один из методов из Builder :

  • ПОЛУЧИТЬ()
  • СООБЩЕНИЕ(тело Процессора тела)
  • PUT(тело процессора тела)
  • УДАЛИТЬ(тело тела процессора)

Мы подробно рассмотрим Процессор тела позже. Теперь давайте просто создадим очень простой пример запроса GET :

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .GET()
  .build();

Этот запрос имеет все параметры, требуемые HttpClient . Однако иногда нам нужно добавить дополнительные параметры в наш запрос; вот некоторые важные из них::

  • версия протокола HTTP
  • заголовки
  • тайм-аут

4.3. Настройка версии протокола HTTP

API полностью использует протокол HTTP/2 и использует его по умолчанию, но мы можем определить, какую версию протокола мы хотим использовать.

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .version(HttpClient.Version.HTTP_2)
  .GET()
  .build();

Здесь важно отметить, что клиент вернется, например, к HTTP/1.1, если HTTP/2 не поддерживается.

4.4. Настройка заголовков

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

Мы можем сделать это одним из двух способов:

  • передача всех заголовков в виде пар ключ-значение в метод headers() или с помощью
  • использование метода header() для одного заголовка ключ-значение:
HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .headers("key1", "value1", "key2", "value2")
  .GET()
  .build();

HttpRequest request2 = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .header("key1", "value1")
  .header("key2", "value2")
  .GET()
  .build();

Последний полезный метод, который мы можем использовать для настройки вашего запроса, – это тайм-аут() .

4.5. Установка тайм-аута

Теперь давайте определим, сколько времени мы хотим ждать ответа.

Если установленное время истекает, a Http TimeoutException будет выдано; тайм-аут по умолчанию установлен на бесконечность.

Тайм – аут может быть установлен с помощью Duration object-путем вызова метода timeout() на экземпляре builder:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .timeout(Duration.of(10, SECONDS))
  .GET()
  .build();

5. Настройка тела запроса

Мы можем добавить тело к запросу с помощью методов requestbuilder: POST(тело процессора тела) , PUT(тело процессора тела) и DELETE(тело процессора тела).

Новый API предоставляет ряд готовых реализаций Body Processor , которые упрощают передачу тела запроса:

  • Строковый процессор (считывает тело из Строки , созданной с помощью Http-запроса.Процессор тела.fromString )
  • Процессор входного потока (считывает тело из InputStream , созданного с помощью Http-запроса.Процессор тела.из входного потока )
  • Процессор массива байтов (считывает тело из массива байтов, созданного с помощью Http-запроса|/.Процессор тела.fromByteArray ) Файловый процессор
  • (считывает тело из файла по заданному пути, созданного с помощью Http-запроса|/.Процессор тела.из файла )

В случае, если нам не нужно тело, мы можем просто передать запрос Http.noBody() :

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .POST(HttpRequest.noBody())
  .build();

5.1. Процессор StringBody

Настройка тела запроса с помощью любой реализации Body Processor очень проста и интуитивно понятна.

Например, если мы хотим передать простую Строку в качестве тела, мы можем использовать процессор StringBody .

Как мы уже упоминали, этот объект может быть создан с помощью заводского метода из String() ; он принимает в качестве аргумента только объект String и создает из него тело:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .headers("Content-Type", "text/plain;charset=UTF-8")
  .POST(HttpRequest.BodyProcessor.fromString("Sample request body"))
  .build();

5.2. Процессор InputStreamBody

Для этого InputStream должен быть передан как Поставщик (чтобы сделать его создание ленивым), поэтому он немного отличается от описанного выше StringBodyProcessor .

Однако это также довольно просто:

byte[] sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .headers("Content-Type", "text/plain;charset=UTF-8")
  .POST(HttpRequest.BodyProcessor
   .fromInputStream(() -> new ByteArrayInputStream(sampleData)))
  .build();

Обратите внимание, как мы использовали простой ByteArrayInputStream здесь; это, конечно, может быть любая реализация InputStream .

5.3. Процессор байтовых массивов

Мы также можем использовать Процессор массива байтов и передать массив байтов в качестве параметра:

byte[] sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .headers("Content-Type", "text/plain;charset=UTF-8")
  .POST(HttpRequest.BodyProcessor.fromByteArray(sampleData))
  .build();

5.4. Файловый процессор

Для работы с файлом мы можем использовать предоставленный Файловый процессор ; его фабричный метод принимает путь к файлу в качестве параметра и создает тело из содержимого:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .headers("Content-Type", "text/plain;charset=UTF-8")
  .POST(HttpRequest.BodyProcessor.fromFile(
    Paths.get("src/test/resources/sample.txt")))
  .build();

Мы рассмотрели, как создавать Http-запрос и как задать в нем дополнительные параметры.

Теперь пришло время глубже взглянуть на класс HttpClient , который отвечает за отправку запросов и получение ответов.

6. HttpClient

Все запросы отправляются с помощью HttpClient который может быть создан с помощью метода HttpClient.newBuilder() или путем вызова HttpClient.newHttpClient() .

Он предоставляет множество полезных и самоописывающих методов, которые мы можем использовать для обработки нашего запроса/ответа.

Давайте рассмотрим некоторые из них здесь.

6.1. Настройка прокси-сервера

Мы можем определить прокси-сервер для подключения. Просто вызовите метод proxy() на экземпляре Builder :

HttpResponse response = HttpClient
  .newBuilder()
  .proxy(ProxySelector.getDefault())
  .build()
  .send(request, HttpResponse.BodyHandler.asString());

В нашем примере мы использовали системный прокси-сервер по умолчанию.

6.2. Настройка политики перенаправления

Иногда страница, к которой мы хотим получить доступ, перемещается по другому адресу.

В этом случае мы получим код состояния HTTP 3xx, обычно с информацией о новом URI. HttpClient может автоматически перенаправить запрос на новый URI, если мы установим соответствующую политику перенаправления.

Мы можем сделать это с помощью метода followRedirects() на Builder :

HttpResponse response = HttpClient.newBuilder()
  .followRedirects(HttpClient.Redirect.ALWAYS)
  .build()
  .send(request, HttpResponse.BodyHandler.asString());

Все политики определены и описаны в перечислении HttpClient.Перенаправление .

6.3. Настройка аутентификатора для подключения

Аутентификатор – это объект, который согласовывает учетные данные (HTTP-аутентификация) для подключения.

Он предоставляет различные схемы аутентификации (например, базовую или дайджест-аутентификацию). В большинстве случаев для аутентификации требуется имя пользователя и пароль для подключения к серверу.

Мы можем использовать Аутентификацию по паролю класс, который является просто держателем этих значений:

HttpResponse response = HttpClient.newBuilder()
  .authenticator(new Authenticator() {
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
      return new PasswordAuthentication(
        "username", 
        "password".toCharArray());
    }
}).build()
  .send(request, HttpResponse.BodyHandler.asString());

В приведенном выше примере мы передали значения имени пользователя и пароля в виде открытого текста; конечно, в производственном сценарии это должно быть по-другому.

Обратите внимание, что не каждый запрос должен использовать одно и то же имя пользователя и пароль. Класс Authenticator предоставляет ряд методов getXXX (например, getRequestingSite () ), которые можно использовать для определения того, какие значения должны быть предоставлены.

Теперь мы рассмотрим одну из самых полезных функций new HttpClient – асинхронные вызовы на сервер.

6.4. Отправка запросов – Синхронизация против Асинхронность

Новый HttpClient предоставляет две возможности для отправки запроса на сервер:

  • отправить(…) – синхронно (блоки до тех пор, пока не придет ответ)
  • SendAsync(…) – асинхронно (не ждет ответа, не блокирует)

До сих пор send(. ..) метод естественно ожидает ответа:

HttpResponse response = HttpClient.newBuilder()
  .build()
  .send(request, HttpResponse.BodyHandler.asString());

Этот вызов возвращает объект HttpResponse , и мы уверены, что следующая инструкция из нашего потока приложений будет выполнена только тогда, когда ответ уже здесь.

Однако у него есть много недостатков, особенно когда мы обрабатываем большие объемы данных.

Итак, теперь мы можем использовать SendAsync(. ..) метод – который возвращает CompletableFeatureдля асинхронной обработки запроса :

CompletableFuture> response = HttpClient.newBuilder()
  .build()
  .sendAsync(request, HttpResponse.BodyHandler.asString());

Новый API также может обрабатывать несколько ответов и передавать тела запросов и ответов в потоковом режиме:

List targets = Arrays.asList(
  new URI("https://postman-echo.com/get?foo1=bar1"),
  new URI("https://postman-echo.com/get?foo2=bar2"));
HttpClient client = HttpClient.newHttpClient();
List> futures = targets.stream()
  .map(target -> client
    .sendAsync(
      HttpRequest.newBuilder(target).GET().build(),
      HttpResponse.BodyHandler.asString())
    .thenApply(response -> response.body()))
  .collect(Collectors.toList());

6.5. Настройка Исполнителя для асинхронных вызовов

Мы также можем определить Executor , который предоставляет потоки для использования асинхронными вызовами.

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

ExecutorService executorService = Executors.newFixedThreadPool(2);

CompletableFuture> response1 = HttpClient.newBuilder()
  .executor(executorService)
  .build()
  .sendAsync(request, HttpResponse.BodyHandler.asString());

CompletableFuture> response2 = HttpClient.newBuilder()
  .executor(executorService)
  .build()
  .sendAsync(request, HttpResponse.BodyHandler.asString());

По умолчанию HttpClient использует executor java.util.concurrent.Исполнители.newCachedThreadPool() .

6.6. Определение менеджера файлов cookie

С помощью нового API и конструктора легко установить Менеджер файлов cookie для нашего соединения. Мы можем использовать метод компоновщика CookieManager(CookieManager CookieManager) для определения конкретного клиента CookieManager .

Давайте, например, определим Менеджер файлов cookie , который вообще не позволяет принимать файлы cookie:

HttpClient.newBuilder()
  .cookieManager(new CookieManager(null, CookiePolicy.ACCEPT_NONE))
  .build();

В случае, если ваш Менеджер файлов cookie разрешает хранение файлов cookie, мы можем получить к ним доступ, проверив Менеджер файлов cookie с нашего HttpClient :

httpClient.cookieManager().get().getCookieStore()

Теперь давайте сосредоточимся на последнем классе из Http API – HttpResponse .

7. Объект HttpResponse

Класс HttpResponse представляет ответ от сервера. Он предоставляет ряд полезных методов, но наиболее важными являются два:

  • StatusCode() – возвращает код состояния (тип int ) для ответа ( HttpURLConnection класс содержит возможные значения)
  • body() – возвращает тело для ответа (тип возврата зависит от параметра response BodyHandler , переданного методу send() )

Объект ответа имеет другой полезный метод, который мы рассмотрим, например uri () , заголовки() , трейлеры() и версия() .

7.1. URI объекта ответа

Метод uri() в объекте response возвращает URI , от которого мы получили ответ.

Иногда он может отличаться от URI в объекте запроса, поскольку может произойти перенаправление:

assertThat(request.uri()
  .toString(), equalTo("http://stackoverflow.com"));
assertThat(response.uri()
  .toString(), equalTo("https://stackoverflow.com/"));

7.2. Заголовки из ответа

Мы можем получить заголовки из ответа, вызвав метод headers() на объекте ответа:

HttpResponse response = HttpClient.newHttpClient()
  .send(request, HttpResponse.BodyHandler.asString());
HttpHeaders responseHeaders = response.headers();

Он возвращает Http-заголовки объект в качестве возвращаемого типа. Это новый тип, определенный в пакете jdk.incubator.http , который представляет представление HTTP-заголовков только для чтения.

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

7.3. Получить трейлеры из ответа

Ответ HTTP может содержать дополнительные заголовки, которые включаются после содержимого ответа. Эти заголовки называются заголовками трейлеров.

Мы можем получить их, вызвав метод trailers() on Http-Ответ:

HttpResponse response = HttpClient.newHttpClient()
  .send(request, HttpResponse.BodyHandler.asString());
CompletableFuture trailers = response.trailers();

Обратите внимание, что trailers() метод возвращает CompletableFuture объект.

7.4. Версия ответа

Метод version() определяет, какая версия протокола HTTP использовалась для взаимодействия с сервером.

Помните, что даже если мы определим, что хотим использовать HTTP/2, сервер может ответить через HTTP/1.1.

Версия, в которой сервер ответил, указана в ответе:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .version(HttpClient.Version.HTTP_2)
  .GET()
  .build();
HttpResponse response = HttpClient.newHttpClient()
  .send(request, HttpResponse.BodyHandler.asString());
assertThat(response.version(), equalTo(HttpClient.Version.HTTP_1_1));

8. Http-клиент Java 11

Основным изменением в Java 11 стала стандартизация клиентского API HTTP, реализующего HTTP/2 и WebSocket. Он направлен на замену устаревшего класса HttpURLConnection , который присутствовал в JDK с самых ранних лет Java.

Это изменение было реализовано в рамках JEP 321.

8.1. Основные изменения в рамках JEP 321

  1. Инкубированный HTTP API из Java 9 теперь официально включен в API Java SE. Новые HTTP API можно найти в java.net.HTTP.*
  2. Более новая версия протокола HTTP предназначена для повышения общей производительности отправки запросов клиентом и получения ответов от сервера. Это достигается путем внесения ряда изменений, таких как мультиплексирование потоков, сжатие заголовков и push-обещания.
  3. Начиная с Java 11, API теперь полностью асинхронен (предыдущая реализация HTTP/1.1 была заблокирована). Асинхронные вызовы реализуются с помощью CompletableFuture .Реализация CompletableFuture заботится о применении каждого этапа после завершения предыдущего, поэтому весь этот поток является асинхронным.
  4. Новый API HTTP-клиента предоставляет стандартный способ выполнения сетевых операций HTTP с поддержкой современных веб-функций, таких как HTTP/2, без необходимости добавления сторонних зависимостей.
  5. Новые API обеспечивают встроенную поддержку HTTP 1.1/2 WebSocket. Основные классы и интерфейс, обеспечивающие основные функциональные возможности, включают в себя:
  • Класс HttpClient, java.net.http.HttpClient
  • Класс HttpRequest , java.net.http.HttpRequest
  • Интерфейс HttpResponse , java.net.http.HttpResponse
  • Интерфейс WebSocket , java.net.http.WebSocket

8.2. Проблемы С HTTP-Клиентом Pre Java 11

Существующее HttpURLConnection API и его реализация имели множество проблем:

  • API URLConnection был разработан с использованием нескольких протоколов, которые теперь больше не функционируют (FTP, gopher и т. Д.).
  • API предшествует HTTP/1.1 и слишком абстрактен.
  • Он работает только в режиме блокировки (т. Е. Один поток на запрос/ответ).
  • Это очень трудно поддерживать.

9. Изменения в Http-клиенте с Java 11

9.1. Введение статических заводских классов

Вводятся новые статические фабричные классы Издатели тела , Подписчики тела, и Обработчики тела , которые включают существующие реализации BodyPublisher , BodySubscriber и BodyHandler.

Они используются для выполнения полезных общих задач, таких как обработка тела ответа в виде строки или потоковая передача тела в файл.

Например, в Pre Java 11 мы должны были сделать что-то вроде этого:

HttpResponse response = client.send(request, HttpResponse.BodyHandler.asString());

Который мы теперь можем упростить как:

HttpResponse response = client.send(request, BodyHandlers.ofString());

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

Например, имена методов, такие как fromXxx , используются, когда мы используем их в качестве адаптеров, или имена, такие как ofXxx , когда мы создаем предопределенные обработчики/подписчики.

9.2. Беглые методы для общих типов телосложения

Были введены удобные заводские методы для создания издателей и обработчиков для обработки общих типов тел.

Например, у нас есть следующие методы fluent для создания издателей из байтов, файлов и строк:

BodyPublishers.ofByteArray
BodyPublishers.ofFile
BodyPublishers.ofString

Аналогично, для создания обработчиков из этих общих типов тел мы можем использовать:

BodyHandlers.ofByteArray
BodyHandlers.ofString
BodyHandlers.ofFile

9.3. Другие Изменения API

1. С помощью этого нового API мы будем использовать BodyHandlers.discarding() и BodyHandlers.replacing(значение) вместо discard(замена объекта) :

HttpResponse response1 = HttpClient.newHttpClient()
            .send(request, BodyHandlers.discarding());
HttpResponse response1 = HttpClient.newHttpClient()
            .send(request, BodyHandlers.replacing(value));

2. Новый метод of Lines() в Обработчиках тела добавляется в обработку потоковой передачи тела ответа в виде потока строк.

3. метод from Line Subscriber добавлен в класс Body Handlers , который может использоваться в качестве адаптера между Body Subscriber и текстовым Flow.Subscriber , который анализирует текст строка за строкой.

4. Добавлен новый BodySubscriber.mapping в BodySubscribers класс, который можно использовать для сопоставления одного типа тела ответа с другим, применив данную функцию к объекту тела.

5. В HttpClient.Перенаправление , константы перечисления SAME_PROTOCOL и SECURE политика заменяются новым перечислением NORMAL .

10. Обработка Push-обещаний в HTTP/2

Новый Http-клиент поддерживает push-обещания через Обработчик Push-обещаний интерфейс .

Это позволяет серверу “подталкивать” контент к дополнительным ресурсам клиента при запросе основного ресурса, экономя больше времени и, как следствие, повышая производительность при рендеринге страниц.

Это действительно функция мультиплексирования HTTP/2, которая позволяет нам забыть о связывании ресурсов. Для каждого ресурса сервер отправляет клиенту специальный запрос, известный как push-обещание.

Полученные Push-обещания, если таковые имеются, обрабатываются данным обработчиком Push-обещаний . PushPromiseHnadler с нулевым значением отвергает любые push-обещания.

HttpClient имеет перегруженный метод SendAsync , который позволяет нам обрабатывать такие обещания, как показано в приведенном ниже примере.

Давайте сначала создадим обработчик Push Promise/|:

private static PushPromiseHandler pushPromiseHandler() {
    return (HttpRequest initiatingRequest, 
        HttpRequest pushPromiseRequest, 
        Function, 
        CompletableFuture>> acceptor) -> {
        acceptor.apply(BodyHandlers.ofString())
            .thenAccept(resp -> {
                System.out.println(" Pushed response: " + resp.uri() + ", headers: " + resp.headers());
            });
        System.out.println("Promise request: " + pushPromiseRequest.uri());
        System.out.println("Promise request: " + pushPromiseRequest.headers());
    };
}

Далее, давайте использовать метод SendAsync для обработки этого push-обещания:

httpClient.sendAsync(pageRequest, BodyHandlers.ofString(), pushPromiseHandler())
    .thenAccept(pageResponse -> {
        System.out.println("Page response status code: " + pageResponse.statusCode());
        System.out.println("Page response headers: " + pageResponse.headers());
        String responseBody = pageResponse.body();
        System.out.println(responseBody);
    })
    .join();

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

В этой статье мы изучили API Java 9 HttpClient , который обеспечивает большую гибкость и мощные функции. Полный код, используемый для API HttpClient Java 9, доступен на GitHub .

Мы также изучили новые изменения в Java 11 HttpClient, которые стандартизировали инкубационный HttpClient, представленный в Java 9, с более мощными изменениями. Фрагменты кода, используемые для Http-клиента Java 11, также доступны через Github .

Примечание: В примерах мы использовали примеры конечных точек REST, предоставленные https://postman-echo.com.