HttpRequest.Builder.build: примеры (JAVA)

Примеры и разбор метода build у HttpRequest.Builder
Раздел: Работа с HTTP (HTTP Client, URLConnection)
HttpRequest.Builder.build: HttpRequest

Краткое описание метода

Метод HttpRequest.Builder.build() создает окончательный неизменяемый объект java.net.http.HttpRequest на основе текущего состояния билдера. Вызов применяется после задания необходимых параметров запроса и возвращает готовый экземпляр, пригодный для отправки через HttpClient.

public abstract HttpRequest build()

Аргументы: отсутствуют. Метод использует внутреннее состояние билдера, которое формируется вызовами таких методов, как uri(URI), header(String,String), headers(String...), GET(), POST(HttpRequest.BodyPublisher), method(String, BodyPublisher), timeout(Duration), version(HttpClient.Version), expectContinue(boolean) и др.

Возвращаемое значение: готовый объект HttpRequest, представляющий HTTP-запрос. Полученный объект неизменяемый; повторные вызовы build() на одном билдере допускаются и создают отдельные объекты с текущим состоянием.

Поведение и детали:

  • URI: требование наличия URI при сборке. Отсутствие URI обычно приводит к исключению (см. раздел ошибок).
  • Метод: если явно не установлен, фактическое значение определяется состоянием билдера. При отсутствии явного метода и при отсутствии тела по умолчанию применяется поведение аналогичное GET. Для отправки тела используются POST, PUT или method(...).
  • Тело: если не задан BodyPublisher, по умолчанию используется публикация без тела (BodyPublishers.noBody()).
  • Заголовки: можно добавлять через header или headers. Повторяющиеся заголовки сохраняются в порядке добавления.
  • Версия протокола: если не указана, решение о версии выполняет HttpClient. Билдер хранит предпочтение версии при вызове version(...).
  • Ожидание продолжения: поддержка механизма Expect: 100-continue через expectContinue(true).

Исключения, которые могут возникнуть при вызове build(): IllegalStateException при отсутствии обязательных данных (например, URI), IllegalArgumentException в случае некорректных значений (например, пустое имя метода), NullPointerException при передаче явно недопустимых null в методы билдера. Точные виды исключений зависят от состояния билдера и версий JDK.

Примеры базового применения

Ниже приведены короткие примеры создания запросов. Каждый пример содержит код и ожидаемый вывод при выполнении кода в типичной среде.

1. Простой GET без отправки

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

System.out.println(request.method());
System.out.println(request.uri());
GET
https://example.com/

2. POST с JSON и синхронной отправкой

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://httpbin.org/post"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString("{\"name\":\"Alice\"}"))
    .build();

HttpResponse resp = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body().substring(0, 80) + "...");
200
{"args":{},"data":"{\"name\":\"Alice\"}","files":{},"form":...}

3. Пользовательский метод PATCH

HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://httpbin.org/patch"))
    .method("PATCH", HttpRequest.BodyPublishers.ofString("field=value"))
    .build();

System.out.println(req.method());
System.out.println(req.bodyPublisher().isPresent());
PATCH
true

4. Отправка файла (без фактической отправки в примере кода)

Path file = Paths.get("/path/to/file.bin");
HttpRequest r = HttpRequest.newBuilder()
    .uri(URI.create("https://example.com/upload"))
    .POST(HttpRequest.BodyPublishers.ofFile(file))
    .build();

System.out.println(r.bodyPublisher().isPresent());
true

Аналоги внутри Java и различия

В экосистеме Java есть несколько подходов к формированию HTTP-запросов. Краткое сравнение:

  • java.net.HttpURLConnection - старый встроенный API. Менее удобен, вручную управляет потоками и заголовками, но не требует внешних зависимостей.
  • Apache HttpClient (CloseableHttpClient) - богатый функционал, расширенные возможности конфигурации и совместимость со старыми версиями Java; предпочтительнее при сложных требованиях к прокси и соединениям.
  • OkHttp - популярная библиотека с простым билдером запросов, эффективной реализацией HTTP/2 и кэшированием; часто выбирается в клиентских приложениях и Android.
  • JAX-RS Client и Spring RestTemplate/WebClient - более высокоуровневые абстракции, полезны при интеграции с REST-сервисами и при работе в контейнерах. WebClient обеспечивает реактивную модель.

Выбор зависит от требований: для простоты и интеграции с JDK предпочтителен java.net.http, при необходимости расширенных опций соединения или кросс-платформенной совместимости стоит рассматривать внешние библиотеки.

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

Ниже краткие примеры создания аналогичных HTTP-запросов в популярных языках с указанием отличий.

PHP (curl)

$ch = curl_init('https://httpbin.org/post');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['name' => 'Alice']));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$resp = curl_exec($ch);
curl_close($ch);
var_dump(substr($resp,0,80));
string(80) "{\n  \"args\": {}, \n  \"data\": \"{\"name\":\"Alice\"}\","

JavaScript (Fetch)

fetch('https://httpbin.org/post', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({name: 'Alice'})
}).then(r => r.text()).then(t => console.log(t.slice(0,80)));
{"args":{},"data":"{"name":"Alice"}","files":{},"form":...}

Python (requests)

import requests
r = requests.post('https://httpbin.org/post', json={'name':'Alice'})
print(r.status_code)
print(r.text[:80])
200
{"args":{},"data":"{\"name\":\"Alice\"}","files":{},"form":...}

C# (.NET HttpClient)

using var client = new HttpClient();
var resp = await client.PostAsync("https://httpbin.org/post",
    new StringContent("{\"name\":\"Alice\"}", Encoding.UTF8, "application/json"));
Console.WriteLine((int)resp.StatusCode);
Console.WriteLine((await resp.Content.ReadAsStringAsync()).Substring(0,80));
200
{"args":{},"data":"{\"name\":\"Alice\"}","files":{},"form":...}

Go (net/http)

req, _ := http.NewRequest("POST", "https://httpbin.org/post", strings.NewReader(`{"name":"Alice"}`))
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode)
fmt.Println(string(b)[:80])
200
{"args":{},"data":"{\"name\":\"Alice\"}","files":{},"form":...}

Lua (lua-http / luasocket)

local http = require('socket.http')
local ltn12 = require('ltn12')
local body = {}
local res, code = http.request{
  url = 'https://httpbin.org/post',
  method = 'POST',
  headers = {['Content-Type']='application/json'},
  source = ltn12.source.string('{"name":"Alice"}'),
  sink = ltn12.sink.table(body)
}
print(code)
print(table.concat(body):sub(1,80))
200
{"args":{},"data":"{\"name\":\"Alice\"}","files":{},"form":...}

Отличия от Java: в большинстве языков создание и отправка запроса объединены в один вызов или в простую цепочку вызовов. Java HttpRequest.Builder отделяет построение запроса и отправку, что упрощает тестирование и повторное использование конфигурации.

Типичные ошибки и их проявления

Ниже описаны распространенные ошибки при вызове build() с примерами и результатом.

1. Отсутствие URI

HttpRequest req = HttpRequest.newBuilder()
    .GET()
    .build();
Exception in thread "main" java.lang.IllegalStateException: request URI not set
    at jdk.httpclient/... (stack trace)

2. Некорректное имя метода

HttpRequest.newBuilder()
    .uri(URI.create("https://example.com"))
    .method("", HttpRequest.BodyPublishers.noBody())
    .build();
Exception in thread "main" java.lang.IllegalArgumentException: invalid HTTP method name
    at jdk.httpclient/... (stack trace)

3. Попытка передать null в методы билдера

HttpRequest.newBuilder()
    .uri(null)
    .build();
Exception in thread "main" java.lang.NullPointerException
    at java.base/... (stack trace)

Рекомендации по диагностике: проверка наличия URI перед сборкой, валидация имени метода, использование безопасных фабрик BodyPublishers и обработка возможных исключений в коде, формирующем запрос.

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

Класс HttpRequest и вложенный интерфейс/билдер появились в JDK 11 вместе с java.net.http.HttpClient. С тех пор сигнатура build() осталась стабильной. Основные изменения в экосистеме касались транспорта и возможностей клиента: добавление улучшенной поддержки HTTP/2 и экспериментальная поддержка HTTP/3 в более поздних релизах JDK. Эти изменения влияли на поведение HttpClient и на способы управления версией через version(HttpClient.Version), но метод build() не претерпел существенных изменений.

Расширенные и редкие сценарии использования

Несколько подробных примеров, которые демонстрируют нестандартные и продвинутые случаи применения билдера.

1. Повторное использование билдера для серии запросов

Пример java
HttpRequest.Builder b = HttpRequest.newBuilder()
    .header("User-Agent", "MyClient/1.0")
    .timeout(Duration.ofSeconds(10));

HttpRequest r1 = b.uri(URI.create("https://example.com/a")).GET().build();
HttpRequest r2 = b.uri(URI.create("https://example.com/b")).POST(HttpRequest.BodyPublishers.ofString("x=1")).build();

System.out.println(r1.uri());
System.out.println(r2.method());
https://example.com/a
POST

2. Потоковая отправка большого файла

Пример java
Path big = Paths.get("/data/large.bin");
HttpRequest r = HttpRequest.newBuilder()
    .uri(URI.create("https://upload.example.com/"))
    .timeout(Duration.ofMinutes(5))
    .POST(HttpRequest.BodyPublishers.ofFile(big))
    .build();

// при отправке HttpClient будет считывать файл по мере передачи, что экономит память
(при отправке: возможен длительный процесс; build не производит чтение файла)

3. Асинхронная отправка и комбинирование ответов

Пример java
HttpClient client = HttpClient.newHttpClient();
HttpRequest r = HttpRequest.newBuilder()
    .uri(URI.create("https://httpbin.org/get"))
    .build();

client.sendAsync(r, HttpResponse.BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenAccept(b -> System.out.println(b.substring(0,80)));
{"args":{},"headers":{...},"origin":"...","url":"https://httpbin.org/get"...}

4. Использование Flow.Publisher для реактивной передачи тела

Пример java
Flow.Publisher publisher = new Flow.Publisher<>() {
    public void subscribe(Flow.Subscriber<? super ByteBuffer> s) {
        s.onSubscribe(new Flow.Subscription() {
            public void request(long n) { s.onNext(ByteBuffer.wrap("chunk".getBytes())); s.onComplete(); }
            public void cancel() { }
        });
    }
};

HttpRequest r = HttpRequest.newBuilder()
    .uri(URI.create("https://example.com/stream"))
    .method("POST", HttpRequest.BodyPublishers.ofPublisher(publisher))
    .build();

System.out.println(r.bodyPublisher().isPresent());
true

5. Expect: 100-continue и таймауты

Пример java
HttpRequest r = HttpRequest.newBuilder()
    .uri(URI.create("https://example.com/upload"))
    .expectContinue(true)
    .timeout(Duration.ofSeconds(30))
    .POST(HttpRequest.BodyPublishers.ofString("payload"))
    .build();

System.out.println(r.expectContinue());
System.out.println(r.timeout().get().getSeconds());
true
30

6. Установка предпочтительной версии HTTP

Пример java
HttpRequest r = HttpRequest.newBuilder()
    .uri(URI.create("https://example.com"))
    .version(HttpClient.Version.HTTP_2)
    .build();

System.out.println(r.version().get());
HTTP_2

Пояснения: большинство операций билдера не выполняют сетевых действий. Метод build() лишь формирует объект запроса. Это позволяет выполнять тестирование и подготовку запросов без сетевых вызовов, а также повторно применять шаблоны конфигурации.

джава HttpRequest.Builder.build function comments

En
HttpRequest.Builder.build Builds the HttpRequest