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

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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

В современной веб-разработке практически ни одно приложение не обходится без взаимодействия с удаленными серверами. Будь то интеграция с REST API, загрузка данных или отправка форм — умение правильно работать с HTTP-запросами на Java является обязательным навыком для разработчика. В этой статье мы разберем, как создать надежный HTTP клиент на Java, рассмотрим современные подходы и лучшие практики.

Почему важно использовать правильный HTTP клиент?

Многие начинающие разработчики используют устаревший класс HttpURLConnection, который появился еще в Java 1.1. Хотя он и работает, его API громоздок, неудобен и требует много шаблонного кода. Начиная с Java 11, в стандартной библиотеке появился современный java.net.http.HttpClient, который предлагает:

  • Поддержку HTTP/2 и WebSocket
  • Асинхронные запросы через CompletableFuture
  • Простой и понятный Builder-паттерн
  • Автоматическую обработку кук и редиректов

Основы работы с HttpClient в Java 11+

Рассмотрим базовый пример отправки GET-запроса. Сначала создадим экземпляр клиента, затем сформируем запрос и отправим его.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class SimpleHttpClient {
    public static void main(String[] args) throws Exception {
        // Создаем клиент с таймаутом соединения
        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .build();

        // Формируем GET-запрос
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/data"))
                .timeout(Duration.ofSeconds(30))
                .header("Accept", "application/json")
                .GET()
                .build();

        // Отправляем запрос и получаем ответ
        HttpResponse<String> response = client.send(request, 
                HttpResponse.BodyHandlers.ofString());

        System.out.println("Статус: " + response.statusCode());
        System.out.println("Тело ответа: " + response.body());
    }
}

Обратите внимание на использование BodyHandlers.ofString() — это удобный способ получить тело ответа в виде строки. Если вам нужно обработать JSON, лучше использовать BodyHandlers.ofInputStream() и передать его в парсер.

Отправка POST-запроса с телом

Для отправки данных на сервер (например, при регистрации пользователя) используется POST-запрос. Рассмотрим пример с отправкой JSON:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class PostRequestExample {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        String json = """
                {
                    "name": "Иван Иванов",
                    "email": "ivan@example.com",
                    "age": 30
                }
                """;

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/users"))
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(json))
                .timeout(Duration.ofSeconds(15))
                .build();

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

        if (response.statusCode() == 201) {
            System.out.println("Пользователь создан успешно!");
            System.out.println("ID: " + response.body());
        } else {
            System.out.println("Ошибка: " + response.statusCode());
        }
    }
}

Ключевой момент здесь — использование BodyPublishers.ofString() для передачи тела запроса. Для больших файлов можно использовать BodyPublishers.ofFile() или BodyPublishers.ofInputStream().

Асинхронные запросы для повышения производительности

В реальных приложениях синхронные запросы могут блокировать поток выполнения, что критично для веб-серверов или UI-приложений. HttpClient поддерживает асинхронную отправку через метод sendAsync():

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;

public class AsyncHttpExample {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/data"))
                .GET()
                .build();

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

        // Можно выполнять другие задачи, пока запрос обрабатывается
        System.out.println("Запрос отправлен, ждем ответ...");

        // Получаем результат (блокирует поток, если еще не готов)
        HttpResponse<String> response = futureResponse.get();

        System.out.println("Статус: " + response.statusCode());
        System.out.println("Данные: " + response.body());
    }
}

Для параллельной отправки нескольких запросов используйте CompletableFuture.allOf():

CompletableFuture<HttpResponse<String>> future1 = 
        client.sendAsync(request1, BodyHandlers.ofString());
CompletableFuture<HttpResponse<String>> future2 = 
        client.sendAsync(request2, BodyHandlers.ofString());

CompletableFuture.allOf(future1, future2).join();

HttpResponse<String> response1 = future1.get();
HttpResponse<String> response2 = future2.get();

Обработка ошибок и повторные попытки

Сеть нестабильна, поэтому важно корректно обрабатывать ошибки. Вот пример с повторными попытками при временных сбоях:

import java.net.http.HttpConnectTimeoutException;
import java.net.http.HttpResponse;
import java.time.Duration;

public class RetryExample {
    private static final int MAX_RETRIES = 3;
    private static final Duration RETRY_DELAY = Duration.ofSeconds(2);

    public static HttpResponse<String> sendWithRetry(HttpClient client,
                                                     HttpRequest request) 
            throws Exception {
        for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
            try {
                HttpResponse<String> response = client.send(request,
                        HttpResponse.BodyHandlers.ofString());

                if (response.statusCode() < 500) {
                    return response; // Успех или клиентская ошибка
                }

                System.out.println("Сервер вернул ошибку " + 
                        response.statusCode() + ". Попытка " + attempt);

            } catch (HttpConnectTimeoutException e) {
                System.out.println("Таймаут соединения. Попытка " + attempt);
            }

            if (attempt < MAX_RETRIES) {
                Thread.sleep(RETRY_DELAY.toMillis());
            }
        }

        throw new RuntimeException("Не удалось выполнить запрос после " 
                + MAX_RETRIES +                

Блоги

Рекомендаций по книгам