1.1 Знакомство с HttpClient

Начиная с JDK 11 разработчики платформы Java добавили в JDK новый мощный инструмент для выполнения http-запросов — пакет java.net.http. Он содержит четыре ключевых класса:

  • HttpClient
  • HttpRequest
  • HttpResponse
  • WebSocket

Это очень мощные классы, которые позволяют выполнять все возможные виды запросов по протоколам HTTP, HTTP/2 и WebSocket.

Кроме того, с помощью этих классов можно выполнять как синхронные, так и асинхронные http-запросы.

Выполнение http-запроса состоит из таких частей:

  1. Создание объекта HttpClient
  2. Создание объекта HttpRequеst
  3. Отправка запроса с помощью метода send() или sendAsync()
  4. Обработка ответа HttpResponse

Пример такого запроса:


 HttpClient client = HttpClient.newBuilder()
        .version(Version.HTTP_1_1)
        .followRedirects(Redirect.NORMAL)
        .connectTimeout(Duration.ofSeconds(20))
        .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
        .authenticator(Authenticator.getDefault())
        .build();
 
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body()); 

1.2 Декларативный подход

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


 HttpClient client = HttpClient.newBuilder()
.version(Version.HTTP_1_1)
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
.authenticator(Authenticator.getDefault())
.build();

Как бы выглядел этот код, написанный в классическом стиле:


HttpClient client = HttpClient.new();
client.setVersion(Version.HTTP_1_1);
client.setFollowRedirects(Redirect.NORMAL);
client.setConnectTimeout(Duration.ofSeconds(20));
client.setProxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)));
client.setAuthenticator(Authenticator.getDefault());

При использовании декларативного подхода в коде меняются две вещи. Во-первых, все методы класса HttpClient возвращают свой же объект, что позволяет организовать код в виде цепочек.

Классический код:

HttpClient client = HttpClient.new();
client.setVersion(Version.HTTP_1_1);
client.setFollowRedirects(Redirect.NORMAL);
client.setConnectTimeout(Duration.ofSeconds(20));
client.setAuthenticator(Authenticator.getDefault());
В виде цепочки:

HttpClient client = HttpClient.new() .setVersion(Version.HTTP_1_1) .setFollowRedirects(Redirect.NORMAL). setConnectTimeout(Duration.ofSeconds(20)) .setAuthenticator(Authenticator.getDefault());
Переносим каждый метод на отдельную строку (это один длинный statement)

HttpClient client = HttpClient.new()
.setVersion(Version.HTTP_1_1)
.setFollowRedirects(Redirect.NORMAL)
.setConnectTimeout(Duration.ofSeconds(20))
.setAuthenticator(Authenticator.getDefault());

Во-вторых, у методов убирают префикс set, что позволяет писать код еще компактнее:

Было

HttpClient client = HttpClient.new()
.setVersion(Version.HTTP_1_1)
.setFollowRedirects(Redirect.NORMAL)
.setConnectTimeout(Duration.ofSeconds(20))
.setAuthenticator(Authenticator.getDefault());

Стало

HttpClient client = HttpClient.new()
.version(Version.HTTP_1_1)
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.authenticator(Authenticator.getDefault());
    

Такой код проще читать, хотя сложнее писать.

И еще один важный момент. В этом примере использовался шаблон (pattern) Builder. Бывают сценарии, когда создание объекта — это сложный процесс. Поэтому его предпочитают формализовать: он начинается с вызова условного метода begin() и заканчивается вызовом условного метода end().

В примере, который мы разбирали, метод HttpClient.newBuilder() возвращает объект HttpClient.Builder (это внутренний служебный класс у класса HttpClient). Все методы типа version() вызываются как раз у этого служебного объекта. Ну а вызов метода build() обозначает окончание построения объекта и возвращает объект HttpClient.