TsgcHTTP3Server › Events › OnH3Request

OnH3Request Event

Fires when a complete HTTP/3 request is received so the application can fill the response.

Syntax

property OnH3Request: TsgcHTTP3RequestEvent;
// TsgcHTTP3RequestEvent = procedure(Sender: TObject; const aConnection: TsgcQUICServerConnection; const aRequest: TsgcHTTP3Request; const aResponse: TsgcHTTP3Response) of object

Default Value

—

Parameters

NameTypeDescription
SenderTObjectThe TsgcHTTP3Server instance that raised the event. Cast it to call PushPromise from inside the handler.
aConnectionconst TsgcQUICServerConnectionQUIC connection the request arrived on. Exposes PeerIP, PeerPort, CreatedAt and connection state.
aRequestconst TsgcHTTP3RequestThe received request. Detailed below.
aResponseconst TsgcHTTP3ResponseThe response to fill. Detailed below. Created by the server with StatusCode preset to 200.

Remarks

OnH3Request is raised once the server has received a complete HTTP/3 request: the header section, plus the body for methods that carry one (POST, PUT, PATCH and QUERY). The server creates the response object before firing the event, and when the handler returns it QPACK-encodes the headers and sends the response on the request stream automatically. Do not call SendResponse from inside this event: it would put a second response on the same stream.

Because StatusCode is preset to 200, a handler that only needs to return an empty 200 can leave the response untouched, and an unassigned OnH3Request answers every request with an empty 200.

Requests are multiplexed: several may be in flight on one connection, each on its own QUIC stream, and each raises its own event. The event is raised on the server's background listener thread, so it must not touch the UI directly, and it should return quickly since the connection is not serviced while the handler runs.

Two request shapes never reach this event. A CONNECT that establishes a WebTransport session is routed to OnWebTransportSession instead, and DATA arriving on a stream that already became a tunnel is routed to OnH3TunnelData. A plain CONNECT does reach OnH3Request: answering it with a 2xx status turns its stream into a tunnel.

Reading the request (TsgcHTTP3Request)

MemberTypeDescription
MethodTh3MethodsRequest method: h3meGet, h3meHead, h3mePost, h3mePut, h3meDelete, h3mePatch, h3meOptions, h3meTrace, h3meConnect or h3meQuery. An unrecognized method is reported as h3meGet.
PathstringThe :path pseudo-header, for example /api/time. Includes any query string; the component does not split it for you.
HoststringThe :authority pseudo-header, the HTTP/3 equivalent of the Host header.
SchemestringThe :scheme pseudo-header, normally https.
ProtocolstringThe :protocol pseudo-header of an Extended CONNECT (RFC 9220). Empty when absent.
DataTBytesThe request body, reassembled from the DATA frames. Empty for methods without a body.
ContentTypestringThe content-type header, lifted out of Headers for convenience.
UserAgent, Accept, AuthorizationstringThe matching request headers, also lifted out of Headers.
HeadersTStringListThe remaining regular headers, one entry per header in name=value form with the name lowercased. The headers promoted to their own properties above are not repeated here. Multiple cookie headers are joined into a single cookie entry with "; " as RFC 9114 requires.

Building the response (TsgcHTTP3Response)

The response object is owned by the server: fill it, do not free it and do not keep a reference to it after the handler returns.

MemberTypeDescription
StatusCodeIntegerThe HTTP status sent as the :status pseudo-header. Preset to 200. Set the status here rather than adding a :status entry to Headers, which would be discarded.
ContentTypestringSent as the content-type header when it is not empty. Include the charset for text, for example text/plain; charset=utf-8.
DataTBytesThe response body. Encode strings explicitly, normally with TEncoding.UTF8.GetBytes. Leave it empty for a body-less response.
HeadersTStringListExtra response headers, added in name=value form, one per entry. Names are lowercased for you.
ContentLengthInt64Not used when sending. The content-length header is computed from the actual length of Data, so this property does not need to be set.

Custom headers use name=value, not name: value. An entry without an = is sent as a header with that whole string as its name and an empty value, which is almost never what you want. The Names and Values properties of these two TStringList objects work as expected, since their NameValueSeparator matches the = form the entries are stored in. Values['content-type'] is the recommended accessor: the lookup is case-insensitive, so it finds the header whatever case it was written in. Names[i] returns the name exactly as stored, and the server lowercases header names as RFC 9114 requires, so a comparison like Names[i] = 'Content-Type' still fails. Compare against a lowercased name, or use Values[] and let it handle the case for you.

The server writes :status, and it adds content-type and content-length when applicable, so those never need to be added by hand. Entries that RFC 9114 forbids on the wire are dropped rather than sent: any pseudo-header (a name starting with :) and the connection-specific headers connection, keep-alive, proxy-connection, transfer-encoding and upgrade.

A response to a HEAD request is sent as a header section only: the body is suppressed automatically even when Data is set, so a HEAD handler can build the response exactly like a GET handler.

.NET difference. The .NET implementation declares this event with a connection id string instead of a connection object, and it is never raised. The reason is not a missing codec: the .NET server does implement HTTP/3 framing and QPACK, and it uses them for the SETTINGS handshake and for Extended CONNECT / WebTransport. What is missing is the routing. By default, that is with H3Settings.Settings.WebTransportMaxSessions left at 0, every connection is handled by a path that accepts each incoming stream and discards it, so no request is ever parsed. With WebTransport enabled, a request header section is QPACK-decoded correctly and then dropped, because only a CONNECT carrying the WebTransport :protocol is taken further. The .NET server therefore accepts QUIC connections and raises OnH3Connect and OnH3Disconnect, but never surfaces a request through this event. Use the Delphi/C++Builder implementation to serve HTTP/3 requests.

Example


procedure OnH3RequestEvent(Sender: TObject; const aConnection:
    TsgcQUICServerConnection; const aRequest: TsgcHTTP3Request;
    const aResponse: TsgcHTTP3Response);
begin
  if (aRequest.Path = '/hello') and (aRequest.Method = h3meGet) then
  begin
    aResponse.StatusCode := 200;
    aResponse.ContentType := 'text/plain; charset=utf-8';
    aResponse.Headers.Add('cache-control=no-store'); // name=value
    aResponse.Data := TEncoding.UTF8.GetBytes('Hello HTTP/3');
  end
  else if (aRequest.Path = '/echo') and (aRequest.Method = h3mePost) then
  begin
    // aRequest.Data holds the reassembled request body
    aResponse.StatusCode := 200;
    aResponse.ContentType := aRequest.ContentType;
    aResponse.Data := aRequest.Data;
  end
  else
  begin
    aResponse.StatusCode := 404;
    aResponse.ContentType := 'text/plain; charset=utf-8';
    aResponse.Data := TEncoding.UTF8.GetBytes('Not Found');
  end;
end;

Back to Events