TsgcQUICServer is a low-level QUIC server: it listens for QUIC connections over UDP and exchanges raw application bytes over QUIC streams.
TsgcQUICServer implements the server side of QUIC (RFC 9000, with TLS 1.3 per RFC 9001 and loss recovery per RFC 9002). QUIC is a connection-oriented, multiplexed, always-encrypted transport that runs over UDP. This component exposes it as a byte transport: it hands you the bytes a peer sends and lets you write bytes back. It does not implement any application protocol on top. For HTTP/3, use TsgcHTTP3Server, which descends from this component.
What the component gives you:
Start raises.h3 and otherwise accepts the peer's first offer, and it is not configurable. A peer that offers nothing is rejected.
The QUIC server runs on the OpenSSL 3.5+ built-in QUIC engine by default. It calls OSSL_QUIC_server_method and SSL_new_listener when the loaded OpenSSL library exports them. Otherwise it falls back to the sgc QUIC engine, and with QUICOptions.SSLOptions.IOHandler = iohNativeTLS it always uses the sgc engine with the TLS 1.3 handshake on the native TLS engine. The read-only QUICMode property reports the engine selected at start. See QUIC for the engine comparison.
Follow these steps to configure the component:
1. Create a new instance of TsgcQUICServer.
2. Set the listening Port. It defaults to 80, inherited from the UDP base class, so always set it (4433 and 443 are the usual QUIC choices).
3. Set the certificate and private key on QUICOptions.SSLOptions. Both are PEM files and both are mandatory. For a test certificate:
openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes -subj "/CN=localhost"
4. Handle OnQUICConnect and OnQUICData, and call Start inside a try..except, because configuration errors surface there.
oServer := TsgcQUICServer.Create(nil);
oServer.Port := 4433;
oServer.QUICOptions.SSLOptions.CertFile := 'server.crt';
oServer.QUICOptions.SSLOptions.KeyFile := 'server.key';
oServer.OnQUICConnect := OnQUICConnectEvent;
oServer.OnQUICData := OnQUICDataEvent;
oServer.Start;
procedure TfrmServer.OnQUICDataEvent(Sender: TObject;
const aConnection: TsgcQUICServerConnection; const aData: TBytes);
begin
// echo the received bytes back to the peer
oServer.WriteData(aConnection, aData);
end;
Threading. Every event is raised on the server's background listener thread, and that single thread drives the event loop for all connections. So keep handlers short, and marshal to the main thread before touching the UI.
Lifecycle. Use Start and Stop, or the Active property, which they are implemented on top of and which reports the real listener state. OnStartup and OnShutdown are raised around those transitions, so you can act from a handler or after the call returns, whichever suits you.
TsgcHTTP3Server_Base descends from TsgcQUICServer_Base, so TsgcHTTP3Server is a QUIC server with the HTTP/3 protocol layered on it. Use TsgcQUICServer when you want a raw byte transport with your own protocol on top, and TsgcHTTP3Server when you want to serve HTTP/3.
If you use the HTTP/3 server, note that it claims part of this component's surface: its constructor sets AlwaysMultiStream to True (HTTP/3 needs the client's unidirectional control and QPACK streams, RFC 9114) and wires OnQUICConnect, OnQUICData, OnQUICStreamData and OnQUICDisconnect to its own handlers. Assigning those events, or clearing AlwaysMultiStream, breaks the HTTP/3 layer. OnQUICException is the one exception, the HTTP/3 server only claims it if you have not assigned it yourself.
Two classes complete the API:
| Name | Description |
|---|---|
| QUICOptions | The whole server setup: the mandatory TLS certificate and private key, and the connection limit. |
| Port | UDP port the server binds to. Defaults to 80, so always set it. |
| AlwaysMultiStream | Accept and read every stream the peer opens, not just the first one. |
| Connections | The list of QUIC connections currently accepted by the server. |
| Name | Description |
|---|---|
| Start | Loads OpenSSL, creates the QUIC listener and begins accepting connections. |
| Stop | Stops listening, closes every active connection and releases the QUIC listener. |
| WriteData | Sends application bytes to a connected peer over its default QUIC stream. |
| OpenStream | Opens a new server-initiated QUIC stream on a connection. |
| Name | Description |
|---|---|
| OnQUICConnect | Fires when a new QUIC connection has been accepted and its first stream is ready. |
| OnQUICData | Fires when application data is received from a peer over its default QUIC stream. |
| OnQUICStreamData | Fires with bytes read from an additional QUIC stream, tagged with its stream id. |
| OnQUICDisconnect | Fires when a QUIC connection has been closed. |
An echo server that reports connections and errors. Note that PeerIP is not populated by the Delphi server, because OpenSSL exposes no getter for it, so the session is identified by the connection instance.
uses
sgcQUIC_Server, sgcHTTP;
procedure TfrmServer.StartServer;
begin
oServer := TsgcQUICServer.Create(nil);
oServer.Port := 4433;
// mandatory: QUIC always runs over TLS 1.3
oServer.QUICOptions.SSLOptions.CertFile := 'server.crt';
oServer.QUICOptions.SSLOptions.KeyFile := 'server.key';
oServer.QUICOptions.MaxConnections := 1000;
oServer.OnQUICConnect := OnQUICConnectEvent;
oServer.OnQUICData := OnQUICDataEvent;
oServer.OnQUICDisconnect := OnQUICDisconnectEvent;
oServer.OnQUICException := OnQUICExceptionEvent;
oServer.OnStartup := OnStartupEvent;
// Start raises on a bad configuration, OnStartup fires once it is listening
try
oServer.Start;
except
on E: Exception do
Log('start failed: ' + E.Message);
end;
end;
procedure TfrmServer.StopServer;
begin
oServer.Stop; // OnQUICDisconnect is NOT raised for these connections
Log('QUIC server stopped');
oServer.Free;
end;
procedure TfrmServer.OnStartupEvent(Sender: TObject);
begin
// raised once the listener is bound and accepting
Log('QUIC server listening on UDP port ' + IntToStr(oServer.Port));
end;
procedure TfrmServer.OnQUICConnectEvent(Sender: TObject;
const aConnection: TsgcQUICServerConnection);
begin
Log(Format('connected at %s (%d live)',
[DateTimeToStr(aConnection.CreatedAt), oServer.ConnectionCount]));
end;
procedure TfrmServer.OnQUICDataEvent(Sender: TObject;
const aConnection: TsgcQUICServerConnection; const aData: TBytes);
begin
// aData is a chunk, not a message: frame at the application level
oServer.WriteData(aConnection, aData);
end;
procedure TfrmServer.OnQUICDisconnectEvent(Sender: TObject;
const aConnection: TsgcQUICServerConnection; aCode: Int64; const aReason: string);
begin
// aCode 0 / empty reason on a normal close, otherwise an OpenSSL error code
Log(Format('disconnected (code=%d %s)', [aCode, aReason]));
end;
procedure TfrmServer.OnQUICExceptionEvent(Sender: TObject;
const aConnection: TsgcQUICServerConnection; const aException: Exception);
begin
// aConnection is nil for a listener error, otherwise it is the failing session
if Assigned(aConnection) then
Log('connection error: ' + aException.Message)
else
Log('listener error: ' + aException.Message);
end;
| Requirement | Detail |
|---|---|
| Edition | All-Access edition. |
| Compiler define | QUIC ships disabled. Uncomment {$DEFINE SGC_PACK_HTTP3} in the All-Access block of sgcVer.inc, it is delivered as {.$DEFINE SGC_PACK_HTTP3}. That block defines SGC_QUIC, SGC_HTTP3 and SGC_WEBTRANSPORT, but only when SGC_INDY_LIB is also defined. |
| Unit | sgcQUIC_Server for the classes, sgcHTTP for the registered TsgcQUICServer component. |
| OpenSSL | OpenSSL 3.5 or later with IOHandler = iohOpenSSL. The built-in engine uses the native QUIC listener API (OSSL_QUIC_server_method, SSL_new_listener) and the sgc engine uses the OpenSSL 3.5 QUIC TLS API, neither of which exists in earlier versions. With iohNativeTLS OpenSSL is not needed. QUICOptions.SSLOptions.OpenSSL_Options.APIVersion must stay oslAPI_3_0. |
| TLS | A certificate and a matching private key, both PEM, are mandatory. An encrypted private key is supported, set its passphrase in QUICOptions.SSLOptions.Password. |
| .NET | net8.0 or later, and MsQuic at runtime. The QUIC server exists on every target framework for parity, but on netstandard2.0 Start() throws PlatformNotSupportedException. |