HttpClient.send: примеры (JAVA)

Примеры работы метода HttpClient.send
Раздел: Работа с HTTP (HTTP Client, URLConnection)
HttpClient.send(HttpRequest request, HttpResponse.BodyHandler responseBodyHandler): HttpResponse

Общее описание метода HttpClient.send

Метод send класса java.net.http.HttpClient представляет собой синхронный вызов, выполняющий HTTP(S)-запрос и возвращающий ответ в виде HttpResponse<T>. API появился в Java 11 как современная альтернатива HttpURLConnection. Вызов блокирует текущий поток до получения ответа или до прерывания.

Основная сигнатура:

public <T> HttpResponse<T> send(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler)
        throws IOException, InterruptedException

Аргументы:

  • HttpRequest request - объект запроса, формируемый через HttpRequest.newBuilder(). Включает метод (GET, POST и т.д.), URI, заголовки, тело через HttpRequest.BodyPublisher, таймаут запроса и версию протокола (HTTP/1.1 или HTTP/2).
  • HttpResponse.BodyHandler<T> responseBodyHandler - обработчик тела ответа, который строит T из потока ответа. Набор стандартных реализаций доступен в HttpResponse.BodyHandlers (например, ofString(), ofByteArray(), ofFile(Path), ofInputStream(), fromSubscriber(...) и т.д.).

Возвращаемое значение:

  • HttpResponse<T> - содержит код ответа (statusCode()), заголовки (headers()), тело (body() типа T), URI запроса (uri()), HTTP-версию (version()), предшествующие редиректы (previousResponse()) и при необходимости информацию о SSL-сессии.

Исключения:

  • IOException - ошибки ввода/вывода при подключении или чтении.
  • InterruptedException - если поток был прерван во время ожидания.

Поведенческие особенности:

  • Метод блокирует вызывающий поток; для неблокирующего поведения используется sendAsync.
  • Таймаут соединения и ожидания ответа задается на уровне HttpRequest через timeout(Duration). Отсутствие срока ожидания означает ожидание до разрыва или исключения.
  • Обработка перенаправлений настраивается в HttpClient (например, followRedirects(HttpClient.Redirect.ALWAYS)).
  • При использовании HTTP/2 возможна мультиплексированная передача, но send остается синхронным.

Короткие примеры использования

Пример 1. Простой GET и чтение тела как строку.

HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create("https://postman-echo.com/get?foo=bar"))
        .GET()
        .build();
HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body().substring(0, 80));
200
{"args":{"foo":"bar"},"headers":{...},"url":"https://postman-echo.com/get?foo=bar"}

Пример 2. POST с JSON и чтение ответа как строку.

String json = "{\"name\":\"Ivan\"}";
HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create("https://postman-echo.com/post"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(json))
        .build();
HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body().contains("Ivan"));
200
true

Пример 3. Сохранение ответа в файл.

Path dst = Paths.get("/tmp/example.bin");
HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create("https://httpbin.org/bytes/1024"))
        .GET()
        .build();
HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofFile(dst));
System.out.println(resp.statusCode());
System.out.println(resp.body());
200
/tmp/example.bin

Пример 4. Таймаут запроса.

HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create("https://httpbin.org/delay/5"))
        .timeout(Duration.ofSeconds(2))
        .GET()
        .build();
try {
    client.send(req, HttpResponse.BodyHandlers.ofString());
} catch (IOException e) {
    System.out.println("IO: " + e.getClass().getSimpleName());
} catch (InterruptedException e) {
    System.out.println("Interrupted");
}
IO: HttpTimeoutException

Похожие Java-решения и их особенности

В экосистеме Java доступны альтернативы для HTTP-запросов:

  • HttpClient.sendAsync
  • Возвращает CompletableFuture<HttpResponse<T>>, подходит для неблокирующей работы и параллельных запросов.

  • HttpURLConnection
  • Нативный старый API, совместимость с очень старыми версиями Java, но более низкоуровневый и менее удобный.

  • Apache HttpClient (CloseableHttpClient)
  • Широко используемая библиотека с богатой функциональностью: продвинутый контроль соединений, прокси, retry, фильтры. Часто предпочтительнее при сложных требованиях к конфигурации.

  • OkHttp
  • Производительная сторонняя библиотека, удобная для мобильных и серверных приложений, хорошо работает с HTTP/2 и WebSocket.

Краткое руководство по выбору: для простых и современных приложений предпочтение отдаётся java.net.http.HttpClient. Для требований к тонкой настройке коннект-пула или специфичных расширений возможен выбор Apache HttpClient или OkHttp.

Аналоги в других языках и отличия

Краткие заметки по соответствующим функциям в других языках с примерами и результатом:

PHP (cURL)

$ch = curl_init('https://postman-echo.com/get?x=1');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$res = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo $code; echo substr($res,0,60);
200
{"args":{"x":"1"},"headers":{...}}

JavaScript (fetch, браузер/Node.js)

fetch('https://postman-echo.com/get?x=1')
  .then(r => r.json())
  .then(j => console.log(j.args));
{ x: '1' }

Python (requests)

import requests
r = requests.get('https://postman-echo.com/get?x=1')
print(r.status_code)
print(r.json()['args'])
200
{'x': '1'}

C# (HttpClient)

using var client = new System.Net.Http.HttpClient();
var r = client.GetAsync("https://postman-echo.com/get?x=1").Result;
Console.WriteLine((int)r.StatusCode);
Console.WriteLine(r.Content.ReadAsStringAsync().Result.Substring(0,40));
200
{"args":{"x":"1"},"headers":{...}}

Go (net/http)

resp, _ := http.Get("https://postman-echo.com/get?x=1")
body, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode)
fmt.Println(string(body)[:60])
200
{"args":{"x":"1"},"headers":{...}}

Kotlin (kotlinx-coroutines + ktor)

val client = HttpClient()
runBlocking {
  val resp = client.get("https://postman-echo.com/get?x=1")
  println(resp)
}
{... JSON ...}

Отличие от Java: в большинстве языков фасады запросов являются более лаконичными; Java предлагает строгую типизацию и стандартный клиент в составе JDK начиная с 11, что упрощает переносимость без внешних зависимостей.

Типичные ошибки и причины

  • HttpTimeoutException: возникает при истечении времени ожидания, если в HttpRequest указан timeout или при внутреннем таймауте. Пример ниже.
  • InterruptedException: поток был прерван во время ожидания. Часто проявляется при отмене задачи через Thread.interrupt().
  • IOException (ConnectException, SSLHandshakeException и т.д.): проблемы сети, отказ сертификата, ошибки DNS или разрыв соединения.
  • IllegalArgumentException: некорректно сформированный запрос (например, null URI или неподходящий BodyPublisher).

Пример возникновения таймаута:

HttpClient c = HttpClient.newHttpClient();
HttpRequest r = HttpRequest.newBuilder()
    .uri(URI.create("https://httpbin.org/delay/10"))
    .timeout(Duration.ofSeconds(1))
    .GET()
    .build();
try {
    c.send(r, HttpResponse.BodyHandlers.ofString());
} catch (IOException e) {
    System.out.println(e.getClass().getSimpleName());
} catch (InterruptedException e) {
    System.out.println("Interrupted");
}
HttpTimeoutException

Пример прерывания потока:

Thread t = new Thread(() -> {
    try {
        client.send(req, HttpResponse.BodyHandlers.ofString());
    } catch (Exception e) { System.out.println(e.getClass().getSimpleName()); }
});
t.start();
Thread.sleep(100);
t.interrupt();
InterruptedException

Изменения и история API

  • API java.net.http.HttpClient и метод send введены в Java 11 как официальный стандартный HTTP-клиент.
  • Поддержка HTTP/2 и TLS/ALPN предусмотрена с момента включения API; со временем добавлялись исправления стабильности и мелкие улучшения реализации в обновлениях JDK.
  • Начиная с Java 11 API оставался стабильным: сигнатуры send и sendAsync не претерпевали значительных изменений; новые утилиты для BodyHandlers/BodySubscribers добавлялись в мелких релизах.

Расширенные и редкие сценарии применения

Пример 1. Постраничная загрузка больших ответов через InputStream и обработка по частям.

Пример java
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://speed.hetzner.de/100MB.bin"))
    .GET()
    .build();
HttpResponse r = client.send(req, HttpResponse.BodyHandlers.ofInputStream());
try (InputStream in = r.body()) {
    byte[] buf = new byte[8192];
    int read;
    long total = 0;
    while ((read = in.read(buf)) != -1) total += read;
    System.out.println("Downloaded: " + total);
}
Downloaded: 104857600

Комментарий: в этом сценарии память не расходуется под весь ответ, чтение идёт потоково.

Пример 2. Загрузка в файл с атомарной заменой (через временный файл).

Пример java
Path tmp = Files.createTempFile("dl","tmp");
HttpResponse r = client.send(req, HttpResponse.BodyHandlers.ofFile(tmp));
Path dst = Paths.get("/data/archive.bin");
Files.move(tmp, dst, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Saved to " + dst);
Saved to /data/archive.bin

Пример 3. Отправка multipart/form-data (ручная сборка).

Пример java
String boundary = "----JavaBoundary" + System.currentTimeMillis();
String part = "--" + boundary + "\r\n"
    + "Content-Disposition: form-data; name=\"file\"; filename=\"a.txt\"\r\n"
    + "Content-Type: text/plain\r\n\r\n"
    + "Hello multipart\r\n"
    + "--" + boundary + "--\r\n";
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://postman-echo.com/post"))
    .header("Content-Type", "multipart/form-data; boundary=" + boundary)
    .POST(HttpRequest.BodyPublishers.ofString(part))
    .build();
HttpResponse r = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(r.statusCode());
System.out.println(r.body().contains("Hello multipart"));
200
true

Комментарий: для сложных multipart-запросов предпочтение часто отдаётся библиотекам, формирующим тело автоматически, но ручная сборка остаётся универсальной.

Пример 4. Использование кастомного SSLContext (доверие к самоподписанному сертификату).

Пример java
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, new TrustManager[] { new X509TrustManager() {
    public void checkClientTrusted(X509Certificate[] c, String s) {}
    public void checkServerTrusted(X509Certificate[] c, String s) {}
    public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}}, new SecureRandom());
HttpClient cl = HttpClient.newBuilder().sslContext(sc).build();
HttpRequest r = HttpRequest.newBuilder().uri(URI.create("https://self-signed.badssl.com/"))
    .GET().build();
HttpResponse resp = cl.send(r, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
200

Комментарий: отключение проверки сертификатов снижает безопасность и должно применяться только в тестах.

Пример 5. Параллельная отправка множества синхронных запросов через пул потоков (блокировка per thread).

Пример java
ExecutorService pool = Executors.newFixedThreadPool(8);
List<Callable<Integer>> tasks = IntStream.range(0,50).mapToObj(i -> (Callable<Integer>)(() -> {
    HttpRequest rq = HttpRequest.newBuilder().uri(URI.create("https://postman-echo.com/get?i="+i)).GET().build();
    HttpResponse<String> rr = client.send(rq, HttpResponse.BodyHandlers.ofString());
    return rr.statusCode();
})).collect(Collectors.toList());
List<Future<Integer>> fut = pool.invokeAll(tasks);
System.out.println("Submitted: " + fut.size());
pool.shutdown();
Submitted: 50

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

джава HttpClient.send function comments

En
HttpClient.send Sends an HTTP request and returns the response