JEP 517: HTTP/3 for the HTTP Client API
HTTP/3 для HTTP Client API
| Ответственный | Daniel Fuchs |
| Тип | Feature |
| Область | SE |
| Статус | Closed / Delivered |
| Выпуск | 26 |
| Компонент | core-libs / java.net |
| Обсуждение | net dash dev at openjdk dot org |
| Трудоёмкость | L |
| Длительность | L |
| Связан с | JEP 321: HTTP Client API |
| Рецензенты | Alan Bateman, Bradford Wetmore, Paul Sandoz |
| Одобрен | Paul Sandoz |
| Создан | 2022/08/05 14:58 |
| Обновлён | 2026/01/21 11:04 |
| Задача | 8291976 |
Аннотация
Обновить HTTP Client API, чтобы он поддерживал протокол HTTP/3, и библиотеки и приложения могли взаимодействовать с HTTP/3-серверами при минимальных изменениях кода.
Цели
-
Обновить HTTP Client API, чтобы он отправлял и получал HTTP/3-запросы и HTTP/3-ответы.
-
Обойтись лишь небольшими изменениями в HTTP Client API и в коде приложений.
-
Не менять версию протокола по умолчанию с HTTP/2 на HTTP/3, а дать разработчикам возможность включать HTTP/3 явно.
Что не является целью
-
Предоставлять API для протокола QUIC, на котором основан HTTP/3, не является целью.
-
Поддерживать сторонних провайдеров защищённых сокетов не является целью.
-
Предоставлять серверную реализацию протокола HTTP/3 не является целью.
-
Обновлять обработчик протокола, который использует устаревший API java.net.URL, поддерживающий только HTTP/1.1, не является целью.
Мотивация
JEP 321 (JDK 11) добавил в платформу Java современный HTTP Client API. Этот API поддерживает HTTP/1.1 и HTTP/2 и был спроектирован так, чтобы поддерживать будущие версии протокола с минимальными изменениями. По умолчанию API предпочитает HTTP/2, но прозрачно переходит на HTTP/1.1, если целевой сервер не поддерживает HTTP/2.
С HTTP Client API легко писать код, который взаимодействует с HTTP-серверами. Например, чтобы отправить GET-запрос на https://openjdk.org/ и получить ответ в виде строки:
import java.net.http.*;
...
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/"))
.GET().build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assert response.statusCode() == 200;
String htmlText = response.body();
Здесь в использовании API нет ничего явного, что зависело бы от версии протокола HTTP. Код приложения не зависит от протокола.
К сожалению, HTTP Client API не поддерживает последнюю версию протокола HTTP. HTTP/3, преемник HTTP/2, был стандартизирован в 2022 году организацией Internet Engineering Task Force (IETF). HTTP/3 использует надёжный протокол транспортного уровня QUIC вместо TCP. QUIC защищается с помощью Transport Layer Security (TLS) версии 1.3.
Поддержка HTTP/3 позволила бы приложениям, использующим HTTP Client API, получить преимущества многих улучшений, которые даёт протокол HTTP/3, в том числе:
- потенциально более быстрые рукопожатия,
- отсутствие проблем, связанных с перегрузкой сети, например блокировки начала очереди (head-of-line blocking), и
- более надёжный транспорт, особенно в средах с высокой долей потери пакетов.
HTTP/3 уже поддерживается большинством веб-браузеров и развёрнут примерно на трети всех веб-сайтов. Платформа Java должна поддерживать его для использования на стороне клиента.
Описание
Чтобы отправить запрос по HTTP/3, вы должны явно включить его использование.
Для этого можно установить версию протокола объекта HttpClient в HTTP/3, и тогда все запросы, отправляемые этим клиентом, по умолчанию будут предпочитать HTTP/3:
var client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
Либо можно задать предпочтительную версию протокола для отдельного объекта HttpRequest:
var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/"))
.version(HttpClient.Version.HTTP_3)
.GET().build();
Больше никаких изменений не требуется. Выбрав HTTP/3 в качестве предпочтительной версии в запросе или в клиенте, вы отправляете запрос обычным образом. Если целевой сервер не поддерживает HTTP/3, то запрос по умолчанию будет прозрачно переведён на HTTP/2 или даже на HTTP/1.1, в зависимости от ситуации.
Согласование версий протокола HTTP
Заранее определить, поддерживает ли целевой сервер HTTP/3, невозможно. Также невозможно перевести существующее соединение HTTP/1.1 или HTTP/2 на соединение HTTP/3, поскольку HTTP/1.1 и HTTP/2 построены поверх TCP-потоков, а QUIC, на котором работает HTTP/3, построен поверх UDP-датаграмм.
Как же тогда HTTP Client API определяет, может ли он использовать HTTP/3 для данного целевого сервера? Есть четыре основных подхода:
-
Отправить первый запрос по HTTP/3. Если соединение HTTP/3 не удаётся установить за разумное время, откатиться на HTTP/2 или HTTP/1.1. (Так происходит, когда у объекта
HttpRequestпредпочтительная версияHTTP_3.) -
Отправить первый запрос, пытаясь параллельно установить и соединение HTTP/3, и соединение HTTP/2 или HTTP/1.1. Использовать протокол того соединения, которое установится первым. (Так происходит, когда у объекта
HttpClientпредпочтительная версияHTTP_3, а у объектаHttpRequestпредпочтительная версия не задана.) -
Отправить первый запрос по HTTP/2 или HTTP/1.1. Если ответ сервера указывает, что HTTP/3 доступен как альтернативный сервис, использовать HTTP/3 для всех последующих запросов. (Так происходит, когда вы указываете [
Http3DiscoveryMode.ALT_SVC] в качестве значения параметра запросаH3_DISCOVERYи хотя бы у одного из объектовHttpClientилиHttpRequestпредпочтительная версияHTTP_3.) -
Отправлять все запросы по HTTP/3. Если сервер не отвечает по HTTP/3, завершиться с ошибкой, не откатываясь на HTTP/1.1 или HTTP/2. (Так происходит, когда вы указываете
Http3DiscoveryMode.HTTP_3_URI_ONLYв качестве значения параметра запросаH3_DISCOVERYи хотя бы у одного из объектовHttpClientилиHttpRequestпредпочтительная версияHTTP_3.)
У этих подходов есть свои компромиссы: первый требует ожидания тайм-аута; второй может загрузить реализацию HTTP/3 и установить соединение HTTP/3, но больше никогда его не использовать; третий требует поначалу использовать HTTP/2 или HTTP/1.1; а четвёртый работает, только если вы заранее знаете, что целевой сервер поддерживает HTTP/3.
Поскольку HTTP/3 пока развёрнут не так широко, как HTTP/2 и HTTP/1.1, ни один подход не подойдёт для всех случаев. Поэтому сейчас мы не предлагаем делать HTTP/3 версией протокола по умолчанию, хотя можем сделать это в будущем.
Возможные дальнейшие улучшения
-
Дать возможность ограниченной настройки и оптимизации реализации HTTP/3, например с помощью специфичных для JDK системных свойств или, возможно, небольших новых API.
-
Определить новые подклассы
IOExceptionиSSLExceptionдля исключительных ситуаций, специфичных для HTTP/3. -
Расширить интерфейс
PushPromiseHandler, чтобы поддержать возможность HTTP/3 совместно использовать promise-ответы в разных потоках запросов/ответов в рамках одного соединения. -
Добавить в API
HttpClient.BuilderиHttpRequest.Builderпараметры конфигурации для управления обнаружением целевого сервера.
Тестирование
Мы проведём обширное модульное и интеграционное тестирование. Мы также проверим взаимодействие с серверными реализациями HTTP/3, включая Netty, quic-go, quiche, Neqo и nghttp3.
Риски и допущения
Эта первая реализация HTTP/3 не будет поддерживать провайдеры защищённых сокетов, кроме провайдера по умолчанию, SunJSSE. Для поддержки сторонних провайдеров защищённых сокетов потребовалось бы добавить методы в SPI провайдеров, а затем разработчикам, сопровождающим такие провайдеры, пришлось бы реализовать эти методы. Мы можем заняться этим в дальнейшей работе.
Зависимости
Протокол QUIC определяется следующими документами:
- RFC 8999: Version-Independent Properties of QUIC
- RFC 9000: A UDP-Based Multiplexed and Secure Transport
- RFC 9001: Using TLS to Secure QUIC
- RFC 9002: QUIC Loss Detection and Congestion Control
Протокол HTTP/3 определяется следующими документами:
Эти RFC представляют интерес для обнаружения конечных точек QUIC и измерения MTU сетевого пути. Первая реализация поддерживает HTTP Alternative Services, но Path MTU Discovery не реализован.
- RFC 7838: HTTP Alternative Services
- RFC 8899: Packetization Layer Path MTU Discovery for Datagram Transports
- RFC 4821: Packetization Layer Path MTU Discovery
Интерес представляют ещё два RFC, которые также поддерживаются первой реализацией: