Перейти к основному содержимому

Публикация с помощью Java HttpClient

· 5 мин. чтения

1. Обзор

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

Мы можем использовать его для отправки HTTP-запросов и получения их ответов. До Java 11 нам приходилось полагаться на элементарную реализацию URLConnection или сторонние библиотеки, такие как Apache HttpClient .

В этом руководстве мы рассмотрим отправку POST-запросов с помощью Java HttpClient . Мы покажем, как отправлять как синхронные, так и асинхронные запросы POST, а также параллельные запросы POST. Кроме того, мы проверим, как добавлять параметры аутентификации и тела JSON в запросы POST.

Наконец, мы увидим, как загружать файлы и отправлять данные формы. Поэтому мы рассмотрим большинство распространенных вариантов использования.

2. Подготовка POST-запроса

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

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

HttpClient client = HttpClient.newHttpClient();

HttpClient будет использовать HTTP/2 по умолчанию. Он также автоматически понизится до HTTP/1.1, если сервер не поддерживает HTTP/2.

Теперь мы готовы создать экземпляр HttpRequest из его построителя. Позже мы воспользуемся экземпляром клиента для отправки этого запроса. Минимальные параметры для POST-запроса — это URL-адрес сервера, метод запроса и тело:

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(serviceUrl))
.POST(HttpRequest.BodyPublishers.noBody())
.build();

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

3. Отправка POST-запроса

Теперь, когда мы подготовили запрос POST, давайте рассмотрим различные варианты его отправки.

3.1. Синхронно

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

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString())

Утилита BodyHandlers реализует различные полезные обработчики, такие как обработка тела ответа как строки или потоковая передача тела ответа в файл. После получения ответа объект HttpResponse будет содержать статус ответа, заголовки и тело:

assertThat(response.statusCode())
.isEqualTo(200);
assertThat(response.body())
.isEqualTo("{\"message\":\"ok\"}");

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

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

CompletableFuture<HttpResponse<String>> futureResponse = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

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

HttpResponse<String> response = futureResponse.get();
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.body()).isEqualTo("{\"message\":\"ok\"}");

3.3. Одновременно

Мы можем комбинировать Streams с CompletableFutures , чтобы выдавать несколько запросов одновременно и ждать их ответов :

List<CompletableFuture<HttpResponse<String>>> completableFutures = serviceUrls.stream()
.map(URI::create)
.map(HttpRequest::newBuilder)
.map(builder -> builder.POST(HttpRequest.BodyPublishers.noBody()))
.map(HttpRequest.Builder::build)
.map(request -> client.sendAsync(request, HttpResponse.BodyHandlers.ofString()))
.collect(Collectors.toList());

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

CompletableFuture<List<HttpResponse<String>>> combinedFutures = CompletableFuture
.allOf(completableFutures.toArray(new CompletableFuture[0]))
.thenApply(future ->
completableFutures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));

Поскольку мы объединили все ответы, используя методы allOf и join , мы получили новый CompletableFuture , который содержит наши ответы:

List<HttpResponse<String>> responses = combinedFutures.get();
responses.forEach((response) -> {
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.body()).isEqualTo("{\"message\":\"ok\"}");
});

4. Добавление параметров аутентификации

Мы можем установить аутентификатор на уровне клиента для HTTP-аутентификации для всех запросов :

HttpClient client = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
"foreach",
"123456".toCharArray());
}
})
.build();

Однако HttpClient не отправляет базовые учетные данные, пока не будет запрошен для них с заголовком WWW-Authenticate с сервера.

Чтобы обойти это, мы всегда можем создать и отправить основной заголовок авторизации вручную:

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(serviceUrl))
.POST(HttpRequest.BodyPublishers.noBody())
.header("Authorization", "Basic " +
Base64.getEncoder().encodeToString(("foreach:123456").getBytes()))
.build();

5. Добавление тела

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

5.1. JSON-текст

Утилита BodyPublishers реализует различные полезные средства публикации, такие как публикация тела запроса из строки или файла. Мы можем опубликовать данные JSON как String , преобразованные с использованием набора символов UTF-8:

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(serviceUrl))
.POST(HttpRequest.BodyPublishers.ofString("{\"action\":\"hello\"}"))
.build();

5.2. Загрузка файлов

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

Path file = tempDir.resolve("temp.txt");
List<String> lines = Arrays.asList("1", "2", "3");
Files.write(file, lines);

HttpClient предоставляет отдельный метод BodyPublishers.ofFile для добавления файла в тело POST . Мы можем просто добавить наш временный файл в качестве параметра метода, а API позаботится об остальном:

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(serviceUrl))
.POST(HttpRequest.BodyPublishers.ofFile(file))
.build();

5.3. Отправка форм

В отличие от файлов, HttpClient не предоставляет отдельного метода для публикации данных формы. Поэтому нам снова нужно использовать метод BodyPublishers.ofString :

Map<String, String> formData = new HashMap<>();
formData.put("username", "foreach");
formData.put("message", "hello");

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(serviceUrl))
.POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
.build();

Однако нам нужно преобразовать данные формы из Map в String , используя пользовательскую реализацию:

private static String getFormDataAsString(Map<String, String> formData) {
StringBuilder formBodyBuilder = new StringBuilder();
for (Map.Entry<String, String> singleEntry : formData.entrySet()) {
if (formBodyBuilder.length() > 0) {
formBodyBuilder.append("&");
}
formBodyBuilder.append(URLEncoder.encode(singleEntry.getKey(), StandardCharsets.UTF_8));
formBodyBuilder.append("=");
formBodyBuilder.append(URLEncoder.encode(singleEntry.getValue(), StandardCharsets.UTF_8));
}
return formBodyBuilder.toString();
}

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

В этой статье мы рассмотрели отправку POST-запросов с использованием Java HttpClient API, представленного в Java 11 .

Мы узнали, как создать экземпляр HttpClient и подготовить POST-запрос. Мы увидели, как отправлять подготовленные запросы синхронно, асинхронно и параллельно. Далее мы также увидели, как добавить основные параметры аутентификации.

Наконец, мы рассмотрели добавление тела к запросу POST. Мы рассмотрели полезные нагрузки JSON, загрузку файлов и отправку данных формы.

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