TsgcQUICServer

TsgcQUICServer is a low-level QUIC server: it listens for QUIC connections over UDP and exchanges raw application bytes over QUIC streams.

Introduction

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:

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.

Relationship with TsgcHTTP3Server

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.

Reference

Two classes complete the API:

Key Properties

NameDescription
QUICOptionsThe whole server setup: the mandatory TLS certificate and private key, and the connection limit.
PortUDP port the server binds to. Defaults to 80, so always set it.
AlwaysMultiStreamAccept and read every stream the peer opens, not just the first one.
ConnectionsThe list of QUIC connections currently accepted by the server.

Key Methods

NameDescription
StartLoads OpenSSL, creates the QUIC listener and begins accepting connections.
StopStops listening, closes every active connection and releases the QUIC listener.
WriteDataSends application bytes to a connected peer over its default QUIC stream.
OpenStreamOpens a new server-initiated QUIC stream on a connection.

Key Events

NameDescription
OnQUICConnectFires when a new QUIC connection has been accepted and its first stream is ready.
OnQUICDataFires when application data is received from a peer over its default QUIC stream.
OnQUICStreamDataFires with bytes read from an additional QUIC stream, tagged with its stream id.
OnQUICDisconnectFires when a QUIC connection has been closed.

Complete Example

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;

Requirements

RequirementDetail
EditionAll-Access edition.
Compiler defineQUIC 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.
UnitsgcQUIC_Server for the classes, sgcHTTP for the registered TsgcQUICServer component.
OpenSSLOpenSSL 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.
TLSA certificate and a matching private key, both PEM, are mandatory. An encrypted private key is supported, set its passphrase in QUICOptions.SSLOptions.Password.
.NETnet8.0 or later, and MsQuic at runtime. The QUIC server exists on every target framework for parity, but on netstandard2.0 Start() throws PlatformNotSupportedException.