JEP draft: Support HTTP/3 in the HttpClient
Owner | Daniel Fuchs |
Type | Feature |
Scope | SE |
Status | Submitted |
Component | core-libs / java.net |
Effort | L |
Duration | L |
Relates to | JEP 321: HTTP Client API |
Reviewed by | Alan Bateman, Bradford Wetmore, Paul Sandoz |
Created | 2022/08/05 14:58 |
Updated | 2024/09/06 16:30 |
Issue | 8291976 |
Summary
Update the HTTP Client API to support the HTTP/3 protocol. This will allow applications and libraries to interact with HTTP/3 servers and get the benefits of HTTP/3 with minimal code changes.
Goals
- Update the HttpClient API to send and receive HTTP/3 requests and responses.
- Require only minor changes to the HttpClient API and to application code.
- Do not change the default protocol version from HTTP/2 to HTTP/3 but, rather, enable developers to opt-in to HTTP/3.
Non-Goals
- It is not a goal to provide an API for the underlying QUIC protocol on which HTTP/3 is based.
- It is not a goal to support third-party secure-socket providers.
- It is not a goal to provide a server-side implementation of the HTTP/3 protocol
- It is not a goal to upgrade the legacy protocol handler (URLConnection) which only supports HTTP/1.1
Motivation
JEP 321 added a modern HTTP client API to the Java Platform in Java 11. The Client API is protocol agnostic and currently supports versions HTTP/1.1 and HTTP/2 of the HTTP protocol. It is designed to support future HTTP protocol versions with minimal API changes. By default, the preferred version used by the HttpClien API is HTTP/2, though it can be transparently downgraded to HTTP/1.1 if the target server doesn't support HTTP/2.
The HTTP client API makes it easy to write code that interacts with HTTP servers.
For example, the code extract below uses the HTTP Client API to send a GET request to
https://openjdk.org/
and receive the response as a string:
import java.net.http.*;
// ...
static HttpClient httpClient = HttpClient.newBuilder().build();
//...
var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/")).GET().build();
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
assert response.statusCode() == 200;
String htmlText = response.body();
In this example there is nothing explicit in the use of the API that depends on the HTTP protocol version. The application code is agnostic to the protocol.
Unfortunately, the HTTP client API does not support the latest version of the HTTP protocol. HTTP/3 was standardized In 2022 by the Internet Engineering Task Force (IETF). HTTP/3 is an evolution of HTTP/2 based on QUIC (pronounced "quick"), a new transport protocol over the User Datagram Protocol (UDP). Unlike previous versions of HTTP, HTTP/3 doesn't use TCP/IP. The QUIC protocol provides a reliable transport layer secured with the Transport Layer Security (TLS) version 1.3, as specified by QUIC-TLS.
Supporting HTTP/3 will enable applications using the HTTP Client API to benefit from the many improvements offered by the new protocol, such as:
- potentially faster handshakes;
- avoidance of certain network congestion issues; and
- more reliable transport, especially in those environments with poor internet connections causing packet loss.
HTTP/3 is already widely adopted by major companies, supported by many browser implementations. The Java Platform should also support it.
Description
To send a request using HTTP/3, you must opt in to HTTP/3 by
setting the default version of the HttpClient
to HTTP/3 or by explicitly
setting the version of the HttpRequest
to HTTP/3. In the example shown
above, enabling HTTP/3 would simply require selecting HTTP/3 using the
new addition to the API. For instance:
Selecting HTTP_3 either as preferred version for the client:
static HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
Or as explicit preferred version for the request:
var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/"))
.version(HttpClient.Version.HTTP_3)
.GET().build();
Nothing else needs changing. After selecting HTTP_3 as preferred version either in the request or in the client, the request can be sent the usual way.
Whether the first request and response will actually be sent via HTTP/3 depends on
whether the server supports HTTP/3, which the implementation will determine
automatically via the [HTTP alternative services] conventions.
If the target server doesn't support HTTP/3 the request may be transparently
downgraded to HTTP/2 (or HTTP/1.1). We may extend the HttpRequest.Builder
API to support additional configuration to override the default settings.
Only one area of API change is absolutely required, that to override the default protocol version and explicitly request that HTTP/3 be used. However we are also considering the following API enhancements:
- limited configuration for tuning the HTTP/3 implementation, for instance by means of JDK-specific system properties, or possibly by small API changes.
- specific subclasses of
IOException
orSSLException
may need to be introduced for detailing HTTP/3 specific errors. - enhancements to the PushPromiseHandler interface to support the HTTP/3 capability of sharing promise responses between different request/response streams opened on the same connection.
- optional configuration options added to the
HttpClient.Builder
orHttpRequest.Builder
for tuning discovery of the target HTTP/3 endpoint.
Why not make HTTP/3 the default?
We are adding a new constant, HTTP_3
, to the existing HttpClient.Version
enum.
This could cause an IncompatibleClassChangeError
if the new constant reaches a switch
statement compiled before the addition of the constant, when the switch
statement has no default clause. For compatibility reasons, it seems therefore
more prudent to require HTTP_3
to be an opt-in: a caller that opts in
for HTTP_3
should be prepared to receive responses that contain an HTTP_3
version. A caller that didn't opt-in, such as a caller that was coded before the
introduction of the new constant, might not expect it.
In addition, HTTP/3 doesn't define any upgrade mechanism. To figure out whether
a server supports HTTP/3, a client would have to either send the first request
through HTTP/1.1 or HTTP/2 and hope to receive an [alternate service][9] header
or frame, or attempt to initiate a QUIC handshake at the given URL. Because QUIC
is based on UDP, determining that QUIC is not supported by the peer can only be
achieved by waiting for a response until a timeout expires. Making HTTP_3
the
default version would therefore cause a cost to be incurred by each request sent
to a new server.
This policy could be revisited in years to come, if adoption of HTTP/3 becomes more widespread.
Testing
We will do extensive testing, including interoperation testing with HTTP servers that support HTTP/3.
Risks and Assumptions
This first implementation of HTTP/3 will not support secure-socket providers other than the default provider (SunJSSE). Support for [third-party secure-socket providers providers] would require adding methods to the provider SPI, and then the maintainers of such providers would have to implement those methods. We may address this in future work.
Dependencies
The QUIC Protocol is defined by the following RFCs:
- 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
The HTTP/3 protocol is defined by the following RFCs:
- RFC 9204: QPACK: Field Compression for HTTP/3
In addition, the following RFCs are of interest for discovering QUIC endpoints and measuring path MTU:
- RFC 7838: HTTP Alternative Services
- RFC 8899: Packetization Layer Path MTU Discovery for Datagram Transports
- RFC 4821: Packetization Layer Path MTU Discovery
Some other RFCs are also of interest but may not be supported by the first implementation: