TsgcHTTP3Server

TsgcHTTP3Server implements an HTTP/3 server (RFC 9114) that serves HTTP requests over QUIC.

Introduction

TsgcHTTP3Server implements an HTTP/3 server as defined in RFC 9114. HTTP/3 carries HTTP requests and responses over QUIC, a UDP-based transport with TLS 1.3 built into it, so every connection is encrypted and multiplexed without head-of-line blocking: a lost packet stalls only the request stream it belonged to, not the whole connection. By default the server runs on the native QUIC listener provided by OpenSSL 3.5 or later, and falls back to the sgc QUIC engine when that listener is not available (see QUICMode), so no external HTTP/3 library is required.

The component owns the whole HTTP/3 stack above QUIC: it opens the control and QPACK streams, exchanges SETTINGS, decodes QPACK header sections, reassembles the request frames on each stream and raises one event per complete request. Requests are multiplexed, so several may be in flight on a single connection, each on its own QUIC stream.

TLS is mandatory. QUIC has no plaintext mode, so the server cannot start without a certificate and a matching private key. Clients validate that certificate exactly as they would an HTTPS certificate, which means a self-signed certificate works only with clients configured to trust it. HTTP/3 also runs over UDP rather than TCP, so the listening port must be open for UDP in any firewall in between.

Follow the steps below to configure this component:

1. Create a new instance of TsgcHTTP3Server and set the Port the server will listen on. HTTP/3 uses UDP, and the inherited default port is not meaningful for HTTP/3, so always set it. Port 443 is the usual choice in production, since it lets the same authority be advertised through the Alt-Svc header of an HTTPS site.

2. Set the certificate and private key in QUICOptions.SSLOptions. Both are PEM files and both are required, because QUIC always negotiates TLS 1.3.

3. Handle the OnH3Request event and call Start. The aResponse parameter is created by the server with StatusCode preset to 200; set its StatusCode, ContentType and Data, and the server QPACK-encodes and sends it automatically when the handler returns. There is no need to call SendResponse. example:


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

    procedure OnH3RequestEvent(Sender: TObject; const aConnection:
        TsgcQUICServerConnection; const aRequest: TsgcHTTP3Request;
        const aResponse: TsgcHTTP3Response);
    begin
      aResponse.StatusCode := 200;
      aResponse.ContentType := 'text/plain; charset=utf-8';
      aResponse.Data := TEncoding.UTF8.GetBytes('Hello HTTP/3');
    end;

Building the response

A request is answered by filling the TsgcHTTP3Response the server hands to OnH3Request. The object is owned by the server, so fill it and do not free it.

MemberTypeDescription
StatusCodeIntegerSent as the :status pseudo-header. Preset to 200.
ContentTypestringSent as the content-type header when not empty.
DataTBytesThe response body. Encode strings explicitly, normally with TEncoding.UTF8.GetBytes.
HeadersTStringListExtra response headers, one entry per header in name=value form.
ContentLengthInt64Not used when sending: content-length is computed from the length of Data.

The server writes :status and adds content-type and content-length for you, so those never need to be added by hand. Custom headers use name=value and not name: value. Pseudo-headers and the connection-specific headers forbidden by RFC 9114 (connection, keep-alive, proxy-connection, transfer-encoding and upgrade) are dropped rather than sent. A response to a HEAD request is sent as a header section only, with the body suppressed automatically. See OnH3Request for the full request and response reference.

Server push, tunnels and graceful shutdown

Server push. Call PushPromise from inside OnH3Request to promise an associated resource and deliver it on a server-initiated push stream. It returns False when the client has granted no push budget through MAX_PUSH_ID, which is normal and must be checked. Current mainstream browsers do not implement HTTP/3 server push, so it is useful mainly with clients you control.

Extended CONNECT and tunnels. Answering a CONNECT request with a 2xx status turns its request stream into a tunnel: the stream stays open, subsequent DATA frames stop being a request body and arrive at OnH3TunnelData, and TunnelSend writes bytes back. Set Settings.EnableConnectProtocol to advertise Extended CONNECT (RFC 9220), which lets a client send a CONNECT carrying a :protocol pseudo-header. Setting H3Settings.Settings.WebTransportMaxSessions above zero additionally enables WebTransport and raises OnWebTransportSession for each session, which is accepted unless the handler rejects it.

Graceful shutdown. Stop closes connections immediately. To drain instead, send SendGoAway on each connection first: it tells the peer the last request stream the server will handle, so clients stop opening new streams and can retry the rest elsewhere, while in-flight requests finish. Call Stop once they have.

Reference

Key Properties

NameDescription
PortUDP port the HTTP/3 server listens on.
QUICOptionsQUIC transport options, including the mandatory certificate and private key, and connection limits.
H3SettingsHTTP/3 protocol settings advertised to peers: QPACK and field-section limits, Extended CONNECT and WebTransport.
ActiveStarts or stops the HTTP/3 server listener.

Key Methods

NameDescription
StartStarts the QUIC listener and begins accepting HTTP/3 connections.
StopStops the listener and closes all active connections.
SendGoAwaySends a GOAWAY frame to start a graceful connection shutdown.
PushPromisePushes an associated resource to the client from inside OnH3Request.

Key Events

NameDescription
OnH3RequestFires when a complete HTTP/3 request is received so the application can fill the response.
OnH3ConnectFires when a client establishes an HTTP/3 connection.
OnH3DisconnectFires when an HTTP/3 connection is closed.
OnH3ExceptionFires when an exception is raised while servicing an HTTP/3 connection.

Complete Example

The following example starts an HTTP/3 server that routes a couple of paths and returns a JSON or a plain-text body, logs connections, and stops the server cleanly. The response is filled inside OnH3Request and sent automatically by the server.


// start
oServer := TsgcHTTP3Server.Create(nil);
oServer.Port := 4433;
oServer.QUICOptions.SSLOptions.CertFile := 'server.crt';
oServer.QUICOptions.SSLOptions.KeyFile := 'server.key';
oServer.OnH3Request := OnH3RequestEvent;
oServer.OnH3Connect := OnH3ConnectEvent;
oServer.OnH3Disconnect := OnH3DisconnectEvent;
oServer.OnH3Exception := OnH3ExceptionEvent;
oServer.Start;

procedure OnH3ConnectEvent(Sender: TObject; const aConnection:
    TsgcQUICServerConnection);
begin
  Log('HTTP/3 client connected: ' + aConnection.PeerIP + ':' +
    IntToStr(aConnection.PeerPort));
end;

procedure OnH3DisconnectEvent(Sender: TObject; const aConnection:
    TsgcQUICServerConnection; aCode: Int64; const aReason: string);
begin
  Log('HTTP/3 client disconnected: ' + aReason);
end;

procedure OnH3ExceptionEvent(Sender: TObject; const aConnection:
    TsgcQUICServerConnection; const aException: Exception);
begin
  Log('HTTP/3 error: ' + aException.Message);
end;

// answer a request
procedure OnH3RequestEvent(Sender: TObject; const aConnection:
    TsgcQUICServerConnection; const aRequest: TsgcHTTP3Request;
    const aResponse: TsgcHTTP3Response);
begin
  if aRequest.Path = '/api/time' then
  begin
    aResponse.StatusCode := 200;
    aResponse.ContentType := 'application/json';
    aResponse.Data := TEncoding.UTF8.GetBytes(
      '{"time":"' + DateTimeToStr(Now) + '"}');
  end
  else
  begin
    aResponse.StatusCode := 404;
    aResponse.ContentType := 'text/plain; charset=utf-8';
    aResponse.Data := TEncoding.UTF8.GetBytes('Not Found');
  end;
end;

// stop
oServer.Stop;
oServer.Free;

Requirements

RequirementDetail
EditionAll-Access edition.
Compiler defineHTTP/3 ships disabled. Uncomment {$DEFINE SGC_PACK_HTTP3} in sgcVer.inc, which enables SGC_QUIC, SGC_HTTP3 and SGC_WEBTRANSPORT.
IndySGC_INDY_LIB must be enabled: the HTTP/3 defines are nested inside it.
UnitsgcHTTP3_Server, or sgcHTTP for the registered TsgcHTTP3Server component.
OpenSSLOpenSSL 3.5 or later with QUICOptions.SSLOptions.IOHandler = iohOpenSSL. There is no fallback on an older OpenSSL. With iohNativeTLS OpenSSL is not needed.
TransportUDP. The listening port must be open for UDP, not TCP.
.NETThe .NET server accepts QUIC connections (.NET 8.0 or later, MsQuic) and implements the HTTP/3 framing and QPACK layer, which it uses for the SETTINGS handshake and for Extended CONNECT / WebTransport. It still does not serve plain HTTP/3 requests: they are never routed to OnH3Request, and SendResponse, SendGoAway and SendPush are not wired to the framing layer, so they do nothing. Use Delphi/C++Builder to serve HTTP/3 requests.