TsgcHTTP3Client

TsgcHTTP3Client is an HTTP/3 client (RFC 9114) that sends requests over QUIC, supporting the ten HTTP verbs synchronously and asynchronously, server push, Extended CONNECT tunnels and WebTransport.

Introduction

HTTP/3 (RFC 9114) is the third major revision of HTTP. It keeps the semantics you already know, the same verbs, status codes and headers, but replaces the transport underneath. Instead of TCP it runs over QUIC (RFC 9000), a UDP-based transport with TLS 1.3 built into the handshake.

That change buys three things. Encryption is mandatory, so there is no plaintext HTTP/3 and no TLS version to negotiate. Streams are independent, so a lost packet on one request no longer stalls every other request sharing the connection, which is the head-of-line blocking that limits HTTP/2. And the handshake is shorter, so a connection is usable sooner.

Two practical consequences are worth knowing before you start. HTTP/3 travels over UDP, so a firewall that only opens TCP 443 blocks it even though the port number is the same as HTTPS. And headers are compressed with QPACK (RFC 9204) rather than HPACK, which the component handles for you and reports on through QPACKDynamicInserts.

HTTP Verbs

The client implements the ten verbs below. The list is exhaustive: it is the Th3Methods enumeration the component encodes into the :method pseudo-header of every request.

VerbSynchronousAsynchronousStream overloadDefault aContentTypeTypical use
GETGetGetAsyncGet(aURL, aResponseContent)—Retrieve a resource. No request body.
POSTPostPostAsyncPost(aURL, aSource, aResponseContent)application/x-www-form-urlencodedSubmit data, typically to create a record or invoke an action.
PUTPutPutAsyncPut(aURL, aSource, aResponseContent)application/jsonReplace the target resource. Idempotent.
DELETEDeleteDeleteAsyncDelete(aURL, aResponseContent)—Remove the target resource. No request body.
HEADHead———Read status and headers only, no body. Returns TsgcHTTP3Response.
PATCHPatchPatchAsyncPatch(aURL, aSource, aResponseContent)application/jsonApply a partial update to the target resource.
OPTIONSRequestOptionsOptionsAsyncRequestOptions(aURL, aResponseContent)—Inspect the methods and capabilities a resource supports.
TRACETraceTraceAsyncTrace(aURL, aResponseContent)—Loop-back diagnostic. Often disabled by servers.
CONNECTConnectConnectAsync——Request a tunnel. See ExtendedConnect for a tunnel that stays open.
QUERYQueryQueryAsyncQuery(aURL, aSource, aResponseContent)application/x-www-form-urlencodedSafe, idempotent request that carries a body. Suits search and filter payloads.

Two naming points are worth knowing. OPTIONS is sent by the method named RequestOptions, because Options collides with an existing identifier in Delphi and C++Builder; its asynchronous counterpart keeps the protocol name and is OptionsAsync. CONNECT is sent by the Connect(aURL) overload, which is a different method from the Connect(aHost, aPort) overload that opens the QUIC connection.

HEAD is the exception to the pattern: it returns a TsgcHTTP3Response rather than a string, and it has neither an asynchronous variant nor a stream overload. Every other verb except CONNECT also has a stream overload that reads the request body from a stream and writes the response body into one, which avoids building large strings in memory. Stream overloads always send content-type: application/octet-stream.

Synchronous and Asynchronous requests

Every verb can be sent two ways. A synchronous call blocks the calling thread until the response is complete and returns the body directly, bounded by ReadTimeout. An asynchronous call returns immediately, runs the request on a background thread and delivers the reply on OnResponse.

When you use the asynchronous methods, always handle OnError: a background request has no caller to raise to, so without that handler a failure is silent. Remember also that the events fire on the background worker thread, so synchronise before touching VCL or FMX controls.

Follow the steps below to send a synchronous request:

1. Create a new instance of TsgcHTTP3Client. There is no host to assign: Host and Port are read-only and report the current connection.

2. Call a verb method with an absolute URL. The connection is opened on demand from that URL, so calling Connect first is optional. Call Disconnect when finished. example:



    oClient := TsgcHTTP3Client.Create(nil);

    try

      vResponse := oClient.Get('https://www.example.com/');

      ShowMessage(vResponse);

      oClient.Disconnect;

    finally

      oClient.Free;

    end;

To send the request asynchronously, assign OnResponse and call GetAsync; the call returns immediately and the response is delivered on the event. Decode the body with GetDataAsString, which honours the charset in the content-type header. example:



    oClient := TsgcHTTP3Client.Create(nil);

    oClient.OnResponse := OnResponseEvent;

    oClient.OnError := OnErrorEvent;

    oClient.GetAsync('https://www.example.com/');



    procedure TfrmMain.OnResponseEvent(Sender: TObject;

      const aResponse: TsgcHTTP3Response);

    begin

      // fires on the background thread

      Log(aResponse.GetDataAsString);

    end;

Server Push

Server push lets the server send a resource the client never asked for, expecting it will be needed (RFC 9114 Section 4.6). Push is disabled until the client opts in by calling SetMaxPushId, which announces the highest push ID it will accept.

Each push then arrives in two stages. OnPushPromise fires first with the request headers the push will answer, which is the moment to refuse it with CancelPush when the resource is already cached. If it is not refused, the complete pushed response arrives on OnPush with the same push ID.

Extended CONNECT and WebTransport

Extended CONNECT (RFC 9220, the HTTP/3 counterpart of RFC 8441) turns a request stream into a bidirectional tunnel for another protocol. Call ExtendedConnect with the protocol token; it returns True on a 2xx status and keeps the stream open. Write to the tunnel with TunnelSend, and read from it by calling TunnelPump, which surfaces incoming bytes on OnTunnelData. The server must advertise SETTINGS_ENABLE_CONNECT_PROTOCOL, otherwise the call returns False.

WebTransport is that mechanism with the webtransport protocol token and a session object on top. WebTransportConnect performs the handshake and returns a TsgcWebTransportSession, and ServerSupportsWebTransport tells you in advance whether the server supports it. Peer-opened streams are only harvested while WebTransportProcessEvents is being called.

WebTransport sessions carry streams only. Datagrams require the RFC 9221 QUIC DATAGRAM extension, which the OpenSSL 3.5 builtin QUIC engine that ships with the library does not provide, so sending a datagram raises TsgcQUICException rather than lose the payload quietly.

Reference

Key Properties

NameDescription
ConnectTimeoutMaximum time in milliseconds the client waits for the QUIC connection to be established. Default 10000.
ReadTimeoutMaximum time in milliseconds the client waits for a response to arrive. Default 30000.
MaxResponseSizeMaximum response body bytes accepted per request. Default 0, meaning unlimited.
TLSOptionsTLS 1.3 parameters (certificates, verification, OpenSSL) used by the QUIC connection.
SettingsHTTP/3 SETTINGS parameters announced to the server when the connection is established.
PeerSettingsHTTP/3 SETTINGS parameters received from the server. Read-only.

Key Methods

NameDescription
GetSends a synchronous HTTP/3 GET request, returning the body as a string or writing it to a stream.
PostSends a synchronous HTTP/3 POST request with a request body.
QuerySends a synchronous HTTP/3 QUERY request, a safe and idempotent verb that carries a body.
GetAsyncSends a non-blocking GET; the reply arrives on OnResponse.
ExtendedConnectPerforms an Extended CONNECT handshake (RFC 9220) and keeps the request stream open as a tunnel.
DisconnectCloses the HTTP/3 connection and the underlying QUIC transport.

Key Events

NameDescription
OnResponseFires when the client receives the full response (status, headers and body) from the server.
OnErrorFires when a request or connection error occurs. Essential when using the async methods.
OnHTTP3BeforeRequestFires before a request is sent, the hook for adding authorization or custom headers.
OnConnectFires after the HTTP/3 connection has been established.
OnDisconnectFires after the HTTP/3 connection has been closed.

Complete Example

The following example configures the client, sends a GET, a POST with a JSON body and a QUERY request, adds an authorization header to every request and closes the connection.



procedure TfrmMain.Example;

var

  oClient: TsgcHTTP3Client;

  vResponse: string;

begin

  oClient := TsgcHTTP3Client.Create(nil);

  try

    oClient.ConnectTimeout := 10000;

    oClient.ReadTimeout := 30000;

    // refuse any response body larger than 10 MB

    oClient.MaxResponseSize := 10 * 1024 * 1024;

    oClient.TLSOptions.VerifyCertificate := True;

    oClient.OnHTTP3BeforeRequest := OnBeforeRequestEvent;



    vResponse := oClient.Get('https://www.example.com/');

    ShowMessage(vResponse);



    vResponse := oClient.Post('https://www.example.com/api/users',

      '{"name": "John"}', 'application/json');

    ShowMessage(vResponse);



    // QUERY: a safe, idempotent request that carries a body

    vResponse := oClient.Query('https://www.example.com/api/search',

      '{"city": "Madrid"}', 'application/json');

    ShowMessage(vResponse);



    oClient.Disconnect;

  finally

    oClient.Free;

  end;

end;



procedure TfrmMain.OnBeforeRequestEvent(Sender: TObject; var Headers: TStringList);

begin

  // header names must be lowercase in HTTP/3

  Headers.Add('authorization: Bearer ' + vToken);

end;

Requirements