HttpClient.send: примеры (JAVA)
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
- HttpURLConnection
- Apache HttpClient (CloseableHttpClient)
- OkHttp
Возвращает CompletableFuture<HttpResponse<T>>, подходит для неблокирующей работы и параллельных запросов.
Нативный старый API, совместимость с очень старыми версиями Java, но более низкоуровневый и менее удобный.
Широко используемая библиотека с богатой функциональностью: продвинутый контроль соединений, прокси, retry, фильтры. Часто предпочтительнее при сложных требованиях к конфигурации.
Производительная сторонняя библиотека, удобная для мобильных и серверных приложений, хорошо работает с 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 и обработка по частям.
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. Загрузка в файл с атомарной заменой (через временный файл).
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 (ручная сборка).
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 (доверие к самоподписанному сертификату).
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).
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.