HTTP/3

HTTP/3 is the third major version of HTTP, defined in RFC 9114. It keeps HTTP semantics unchanged and replaces the transport with QUIC.

Introduction

HTTP/3 keeps everything you already know about HTTP: the same methods, the same status codes, the same header fields, the same caching and authentication rules. What changes is what carries them. HTTP/1.1 and HTTP/2 run over TCP with TLS layered on top. HTTP/3 runs over QUIC, a UDP-based transport with TLS 1.3 built in.

Because HTTP semantics are unchanged, an application ported from HTTP/1.1 or HTTP/2 to HTTP/3 behaves the same way. You issue a GET, you get a status code and a body back.

RFCTitleWhat it defines
RFC 9114HTTP/3The mapping of HTTP onto QUIC: frames, request and response streams, unidirectional stream types, settings, server push and the HTTP/3 error codes.
RFC 9204QPACK: Field Compression for HTTP/3Header compression designed for QUIC, using a static table plus a dynamic table carried on dedicated encoder and decoder streams.
RFC 9000QUIC transportThe transport HTTP/3 is carried on. See QUIC.

Why not HPACK

HTTP/2 compresses headers with HPACK, which assumes headers arrive in the order they were sent, an assumption TCP guarantees. QUIC streams can be delivered out of order, so HPACK would reintroduce head-of-line blocking at the compression layer. QPACK solves this by carrying dynamic-table updates on separate unidirectional streams and by letting a request block until only the table entries it actually depends on have arrived.

How HTTP/3 compares to HTTP/1.1 and HTTP/2

 HTTP/1.1HTTP/2HTTP/3
SpecificationRFC 9112RFC 9113RFC 9114
TransportTCPTCPQUIC over UDP
EncryptionOptional, TLS layered on topOptional in the spec, TLS in practiceAlways on, TLS 1.3 is part of the transport
MultiplexingNone. One request in flight per connection, so clients open several connectionsMany streams over one TCP connectionMany streams, provided by the transport itself
Head-of-line blockingYes, at the request levelRemoved at the application layer, but a lost TCP segment still stalls every streamRemoved. A lost packet only stalls the streams whose data it carried
Header compressionNone, headers are plain textHPACK (RFC 7541)QPACK (RFC 9204)
Message formatTextBinary framesBinary frames over QUIC streams
Handshake costTCP handshake, then a separate TLS handshakeTCP handshake, then a separate TLS handshakeTransport and TLS combined in one flight, with 0-RTT on a resumed session
Connection migrationNo, the connection is bound to the four-tupleNoYes, the connection is identified by a connection ID
Server pushNoYes, deprecated in practiceYes, driven by push IDs and a client-controlled limit

Supported HTTP methods

The client supports the ten methods declared in the Th3Methods enumeration (sgcHTTP3_Types.pas). Each has a blocking method, and most also have an asynchronous variant. This table is an orientation map, see TsgcHTTP3Client for the full signatures, overloads and default parameter values.

MethodEnumeration valueClient methodAsync variant
GETh3meGetGetGetAsync
HEADh3meHeadHead—
POSTh3mePostPostPostAsync
PUTh3mePutPutPutAsync
DELETEh3meDeleteDeleteDeleteAsync
PATCHh3mePatchPatchPatchAsync
OPTIONSh3meOptionsRequestOptionsOptionsAsync
TRACEh3meTraceTraceTraceAsync
CONNECTh3meConnectConnectConnectAsync
QUERYh3meQueryQueryQueryAsync

Notes on two of them:

Discovery with Alt-Svc

A client cannot assume a server speaks HTTP/3, and there is no HTTP/3 upgrade handshake equivalent to the HTTP/2 one. The usual discovery route is the Alt-Svc response header (RFC 7838): a server reached over HTTP/1.1 or HTTP/2 advertises that the same origin is also available over HTTP/3 at a given host and port, and the client remembers that for future requests. The client parses this header and surfaces it through the OnAltSvc event, so your application can decide when to switch.

Server push

A server can push a response the client did not ask for, so a client fetching a page does not need a second round trip for a resource the server knows it will need. Each push carries a push ID. The client keeps control: it advertises how many pushes it will accept with SetMaxPushId, is notified of an incoming push through OnPushPromise, and can refuse one with CancelPush.

Extended CONNECT

Extended CONNECT (RFC 9220 for HTTP/3, which carries over the mechanism RFC 8441 defined for HTTP/2) turns a request stream into a tunnel for another protocol. It adds a :protocol pseudo-header to the CONNECT method, so the endpoint knows which protocol it is being asked to tunnel.

It is only available when the server advertises SETTINGS_ENABLE_CONNECT_PROTOCOL in its settings. The client exposes ExtendedConnect, which returns False when the server never advertised it, or when the response status is not a 2xx. Once the tunnel is open you write to it with TunnelSend and receive through OnTunnelData.

WebTransport

WebTransport is built on Extended CONNECT. It negotiates a session over an Extended CONNECT stream and then gives the application QUIC streams directly, which is what you want for low-latency, bidirectional, non-request/response traffic. The specification also offers datagrams, but they cannot be carried on the QUIC engine this library ships with, so WebTransport here is streams only. It needs both SETTINGS_ENABLE_CONNECT_PROTOCOL and a non-zero SETTINGS_WEBTRANSPORT_MAX_SESSIONS from the server, which the client checks through ServerSupportsWebTransport. Because a session needs several streams on one QUIC connection, WebTransport requires the builtin OpenSSL QUIC engine, see QUIC. Details are on the WebTransport page.

Components

ComponentDescription
TsgcHTTP3ClientSends HTTP requests over HTTP/3, with blocking and asynchronous methods, Alt-Svc detection, server push, Extended CONNECT tunnels and WebTransport.
TsgcHTTP3ServerAccepts HTTP/3 connections and serves requests over QUIC. It descends from TsgcQUICServer and adds HTTP/3 framing, settings and request dispatch.
WebTransportSessions and streams over HTTP/3, negotiated with Extended CONNECT. Datagrams are not carried on the shipping QUIC engine.

Quick start

Connect to a host and port, then issue requests. The default port is 443.

oClient := TsgcHTTP3Client.Create(nil);
oClient.OnResponse := OnResponseHandler;
oClient.OnAltSvc := OnAltSvcHandler;
oClient.Connect('www.example.com', 443);
vBody := oClient.Get('https://www.example.com/');
oClient.Disconnect;

The server binds a UDP port, loads a certificate and key, and raises an event for every request.

oServer := TsgcHTTP3Server.Create(nil);
oServer.Port := 443;
oServer.QUICOptions.SSLOptions.CertFile := 'cert.pem';
oServer.QUICOptions.SSLOptions.KeyFile := 'key.pem';
oServer.OnH3Request := OnH3RequestHandler;
oServer.Start;

HTTP/3 travels over UDP, so port 443 must be reachable over UDP and not only over TCP. This is the most common reason a working HTTP/2 deployment fails to serve HTTP/3.

The server binds the address in Host. A value holding a colon is an IPv6 literal, and any other value, an IPv4 literal or a host name, is resolved over both families. An empty Host binds the dual-stack wildcard, so one listener serves IPv4 and IPv6 peers on the same port. The client is URL driven, so an IPv6 literal goes in brackets there, for example oClient.Get('https://[::1]:443/').

Requirements

RequirementDetail
EditionAll-Access. The SGC_PACK_HTTP3 pack sits in the SGC_EDT_ALL block of sgcVer.inc.
Pack defineSGC_PACK_HTTP3. It ships commented out in sgcVer.inc, so it must be enabled explicitly before HTTP/3 is compiled in.
Feature definesWith SGC_PACK_HTTP3 and SGC_INDY_LIB both enabled, sgcVer.inc defines SGC_QUIC, SGC_HTTP3 and SGC_WEBTRANSPORT. The HTTP/3 components are compiled under SGC_HTTP3, and the WebTransport members under SGC_WEBTRANSPORT.
IndySGC_INDY_LIB is required. The HTTP/3 defines are nested inside an SGC_INDY_LIB guard.
OpenSSLOpenSSL 3.5 or later, which provides the native QUIC support the components are built on by default. WebTransport additionally requires the builtin OpenSSL QUIC engine. With the TLS IOHandler set to iohNativeTLS, HTTP/3 runs without OpenSSL on the sgc QUIC engine, see SSL without OpenSSL.
UnitssgcHTTP.pas declares the components. HTTP/3 itself lives in the sgcHTTP3_* units, WebTransport in sgcWebTransport.pas, and the transport in the sgcQUIC_* units.

To enable HTTP/3, edit sgcVer.inc and uncomment the pack define inside the All-Access block:

{$IFDEF SGC_EDT_ALL} { ALL-ACCESS }
  {$DEFINE SGC_PACK_HTML}
  {$DEFINE SGC_PACK_HTTP3}
  {.$DEFINE SGC_PACK_WEBRTC}
{$ENDIF}

See also