sgcWebSockets 2026.8: QUIC, HTTP/3, Static OpenSSL and a TLS Overhaul | eSeGeCe Blog

sgcWebSockets 2026.8: QUIC, HTTP/3, Static OpenSSL and a TLS Overhaul

· Releases
sgcWebSockets 2026.8: QUIC, HTTP/3, Static OpenSSL and a TLS Overhaul

sgcWebSockets 2026.8 is the largest release of the year. There are four new transport components, QUIC and HTTP/3 on both the client and the server side, OpenSSL can now be linked inside your executable so a TLS application ships without a single DLL, and sgcHTML grows by more than thirty components, gains a WebBroker and DataSnap dispatcher and adapts to a phone screen.

Underneath, the SChannel TLS layer was taken apart and rebuilt. If you use the Windows TLS stack, this is the release to install: TLSOptions.Version was silently ignored, a revoked certificate was accepted, a TLS 1.3 connection never completed, and a renegotiation replaced the certificate with no check at all. All of that is fixed, and revocation checking, a version floor and client certificate verification on the server are now available. The rest of this post is the guided tour, and each section links to the article that covers the feature in depth.

QUIC and HTTP/3

Four new components bring QUIC (RFC 9000) and HTTP/3 (RFC 9114) to Delphi and C++ Builder: TsgcQUICClient, TsgcQUICServer, TsgcHTTP3Client and TsgcHTTP3Server. They run on the native OpenSSL 3.5 QUIC engine, so there is no third party stack to deploy, and you get TLS 1.3 folded into the handshake, multiplexed streams with no head of line blocking, 0-RTT resumption and connection migration when the client changes network.

uses
  sgcQUIC_Client;

var
  oClient: TsgcQUICClient;
begin
  oClient := TsgcQUICClient.Create(nil);
  oClient.Host := 'www.example.com';
  oClient.Port := 443;
  oClient.OnQUICConnect    := OnQUICConnect;
  oClient.OnQUICStreamData := OnQUICStreamData;

  oClient.Active := True;          // TLS 1.3 handshake in a single flight
  oClient.WriteData('ping');       // send bytes on a QUIC stream
end;

The HTTP/3 client speaks every HTTP verb, does QPACK header compression, works blocking or asynchronously, discovers an HTTP/3 endpoint through Alt-Svc and handles server push.

uses
  sgcHTTP3_Client;

var
  oClient: TsgcHTTP3Client;
  vBody: string;
begin
  oClient := TsgcHTTP3Client.Create(nil);
  oClient.OnResponse := OnResponse;
  oClient.OnAltSvc   := OnAltSvc;

  oClient.Connect('www.example.com', 443);
  vBody := oClient.Get('https://www.example.com/');
end;

Both need the sgcQUIC package with sgcWebSockets Enterprise. Read more: QUIC Client and Server Components and HTTP/3 Client and Server Components.

OpenSSL inside the executable

Deploying libcrypto and libssl next to the application has always been the least pleasant part of shipping a TLS client. From 2026.8 the library can link OpenSSL 3.5.7 statically: add one unit to the uses clause of your project and the DLLs are gone. Remove the unit and the DLLs are loaded again exactly as before, so the choice is a one line change in either direction.

uses
  sgcWebSocket, sgcWebSocket_Classes,
  sgcIdSSLOpenSSL_Static;

It covers clients and servers, 32 and 64 bit, from Delphi XE2 onwards. Read more: Static OpenSSL Linking, No More DLLs.

sgcHTML: WebBroker, DataSnap and thirty new components

sgcHTML pages are no longer tied to the sgcWebSockets server. A new dispatcher component serves them from any WebBroker application, standalone, ISAPI, Apache or CGI, and from DataSnap servers, where the bridge adds live WebSocket updates on the same port as your REST endpoints.

uses
  Web.HTTPApp, sgcHTMX_Engine_Server_WebBroker, sgcHTMX_Router;

// Any WebBroker host: the engine's Owner is the web module,
// so the standard WebBroker dispatcher calls it automatically
FEngine := TsgcHTMX_Engine_Server_WebBroker.Create(WebModule1);
FEngine.Router := FRouter;   // your htmx routes and the page

The component set grew a great deal in this release, and everything is still drawn on the server with no client side library to load:

Pages also adapt to the screen size now. On a phone the side menu folds away behind a button and the content uses the full width, on a desktop nothing changes. Set the new Responsive property to False for the previous fixed layout. Read more: sgcHTML on WebBroker and DataSnap.

The SChannel TLS overhaul

SChannel is the Windows TLS stack, the one you get without deploying OpenSSL at all. It had a set of problems that were invisible from the outside, which is the worst kind. TLSOptions.Version was not read at all on Windows 11 and Windows Server 2022, asking for TLS 1.3 on Windows 10 silently gave you TLS 1.2, and leaving the version undefined switched SSL 3.0, TLS 1.0 and TLS 1.1 back on. A revoked server certificate was accepted even with VerifyCertificate set to True, because the chain was built without asking for any revocation status. After a renegotiation a new certificate was accepted with no check of the chain or the host name. The connections Indy opens on the side, an HTTP redirect to another host or an FTP data channel, came back with an empty configuration, so they ran with verification off and nothing reported it.

All of that is fixed, the negotiated version is now checked when the handshake completes, and three new option groups came with the work: revocation checking, a version floor, and Windows strong crypto.

// client
oClient.TLSOptions.Version     := tls1_3;   // highest version to use
oClient.TLSOptions.SChannel_Options.VersionMin := tls1_2;   // lowest accepted
oClient.TLSOptions.SChannel_Options.UseStrongCrypto := True;
oClient.TLSOptions.SChannel_Options.Revocation.Check   := scrcChainExcludeRoot;
oClient.TLSOptions.SChannel_Options.Revocation.Timeout := 5000;  // ms

// or all of it in one line
oClient.TLSOptions.Preset := tlspSecureDefaults;

The revocation defaults are deliberately forgiving: IgnoreRevocationOffline and IgnoreNoRevocationCheck are True, so switching the check on cannot break a connection that used to work, and the Timeout bounds CRL and OCSP retrieval so an unreachable responder cannot stall the handshake. A certificate that is actually revoked is always rejected.

On the server side, SChannel can now ask the client for a certificate, which it never did before. The client certificate goes through the same checks a client applies to a server certificate, the chain, the dates and the OnSChannelVerifyPeer event, without the host name check.

oServer.SSLOptions.VerifyCertificate := True;
oServer.SSLOptions.VerifyCertificate_Options.FailIfNoCertificate := True;

Two more fixes belong here. A client using SChannel never noticed the connection was gone when the other end dropped it without a close notification, as happens when a proxy restarts, so OnDisconnect never fired and the WatchDog never ran. And a TLS 1.3 connection never completed at all, because the session ticket the server sends right after the handshake left the client waiting for data the server had already sent. The handshake now honours ConnectTimeout instead of covering only the TCP connect, and a close notification is sent before closing, on SChannel, Apple and Android connections alike.

Server limits and the IOCP / EPOLL engines

Servers grew a set of limits that used to be unbounded. The WebSocket server accepts 10,000 simultaneous connections by default instead of an unlimited number, and caps how many control frames a client may send each second (100), so it cannot be flooded with pings. The HTTP/2 server gained MaxRequestSize, and the DTLS component MaxConnections, HandshakeTimeout and IdleTimeout, so a flood of single packets from made up addresses no longer fills the server's memory. Every one of them can be raised or set to 0 for the previous behaviour.

The high performance engines got a long list of repairs: memory and handle leaks and crashes on several cleanup paths, a client dropping the line right after being accepted, a connection released twice when the server was stopped mid cleanup, a use after free with worker threads enabled, and a buffer lost on every message processed. HTTP keep-alive was not working at all, so the connection was closed after each request and the server filled with sockets in TIME_WAIT. On Linux, every HTTP request leaked pieces of the parsed request, and all workers shared one connection queue, so one or two threads did almost all the work.

Two new options round it off, both off by default: keep-alive probes and an idle timeout, so half-open sockets no longer pile up, and round robin distribution of new connections across worker threads.

Server.IOHandlerOptions.IOHandlerType := iohEPOLL;
Server.IOHandlerOptions.EPOLL.TCPKeepAlive.Enabled  := True;
Server.IOHandlerOptions.EPOLL.TCPKeepAlive.Time     := 60;  // seconds idle before probing
Server.IOHandlerOptions.EPOLL.TCPKeepAlive.Interval := 10;  // seconds between probes
Server.IOHandlerOptions.KeepAliveTimeout            := 120; // seconds

Read more: TCPKeepAlive and KeepAliveTimeout for IOCP and EPOLL Servers. Thanks to Andrea for contributing the patch several of the engine fixes are based on.

Protocols: STOMP, MQTT, AMQP and HTTP/2

STOMP received the most work. A graceful Disconnect now waits until the broker confirms it with a receipt, so nothing is lost when closing. Messages are delivered exactly as the broker sent them, using content-length to read the body, so it can contain line breaks and binary zeros. Frames packed several to a WebSocket message are all processed, a frame split across two messages is reassembled, and binary frames are no longer ignored. ACK and NACK send the headers the negotiated version requires, and heart-beats start only after the server confirms the connection.

oSTOMP.DisconnectTimeout := 10000;   // ms to wait for the receipt, 0 = return at once
oSTOMP.ACKEx(vMessageId, vSubscriptionId);
ShowMessage('STOMP ' + oSTOMP.NegotiatedVersion);

MQTT no longer builds packets brokers reject, a CONNECT with a user name and no password or an MQTT 5 packet carrying a larger block of properties, and the MQTT 5 client stopped reading past the end of a packet: a truncated packet used to hand whatever followed in memory to the application as property values. It now honours what the broker tells it, from the Keep Alive returned in the CONNACK to a message arriving with only a Topic Alias. The QoS 2 publishing path sends the correct follow-up confirmation, discards a message the broker rejected instead of retrying forever, and retries on a sensible schedule.

The AMQP 1.0 client is now protected against a bad broker: frames arriving in small pieces, an invalid header size, messages nested too deeply, a small message crafted to expand into a huge amount of memory. The default maximum frame size for AMQP 0.9.1 and 1.0 is 1 MB instead of practically unlimited. HTTP/2 header compression is protected against crafted headers, the connection rejects a DATA frame for a stream never opened, a flood of PRIORITY frames, stream number reuse and empty continuation floods, and the server rejects header names and values containing line breaks or nulls, which prevents request smuggling. The memory growth from reset streams and the "CONCURRENT STREAM limit has been exceeded" after about 100 requests are both fixed.

MCP server

Tools can now expose a complete JSON Schema for their input, declaring arrays with items, enums, integer and nullable types, nested objects and additionalProperties, which some MCP clients require and would otherwise reject. The server also exposes the metadata of the 2025-11-25 protocol: the readOnlyHint annotation and annotation title, tool icons, server title, website and icons, and the tools/list task support declaration.

Two fixes matter if you already run one. The server wrote its internal connection id as a message on the event stream a client opens with GET, which clients such as VS Code GitHub Copilot reported as "Failed to parse message". And the origin check only ran when the request carried a header browsers never send, so it never ran for the case it was meant to stop: a web page the user visited could reach an MCP server on their own machine, list its tools, run them and read the results. The origin is now checked on every request, including the browser's preliminary check.

MCPServer.MCPOptions.HTTPStreamable.AllowedOrigins.Add('https://*.example.com');

Crypto exchanges

Two customer reports drove most of this. A request that fails with an HTTP error status now reports the headers the server answered with, so Retry-After on a 429, or the rate limit counters exchanges return, can finally be read. The ready made API clients raise EsgcHTTPAPIProtocolException, which descends from the exception raised before, so existing handlers keep working.

try
  vResponse := oBinance.GetAggregateTrades('BTCUSDT');
except
  on E: EsgcHTTPAPIProtocolException do
  begin
    if E.ErrorCode = 429 then
      Sleep(E.RetryAfterMs);
    vWeight := E.GetHeader('X-MBX-USED-WEIGHT-1M');
    // E.ResponseHeaders holds the complete list
  end;
end;

Every exchange client used to resend its whole subscription list in one burst after reconnecting, on a connection a few milliseconds old, so an exchange that limits messages per second closed it straight away and the cycle repeated. The replay is now paced, and on Binance sent as combined frames. There is also a new client side throttle on the messages you send.

oBinance.Throttle.Enabled     := True;
oBinance.Throttle.MaxMessages := 4;      // per window
oBinance.Throttle.IntervalMs  := 1000;
// paced reconnect replay is on by default:
// oBinance.Throttle.PaceResubscribe := False;  restores the old burst

// subscribe a whole watchlist with ONE frame
oBinance.SubscribeStreams(['btcusdt@trade', 'ethusdt@trade', 'bnbusdt@trade']);

Binance retired the listenKey endpoints the Spot user data stream was built on, so account, order and balance updates now come from the Binance WebSocket API, over a second connection the component opens by itself and renews after a reconnection. The events arrive in the same shape, so your handlers keep working, but the new subscription is signed: Binance.ApiSecret must now be set as well as Binance.ApiKey. Binance.us and Futures still use a listenKey. USD-M Futures market data is now served from two addresses, selected with the new Binance.FuturesStreamEndpoint property (bfsePublic by default, bfseMarket for aggregate trades, mark price, kline and liquidation streams).

Also fixed: OKX prices and sizes were rounded to five decimals before being sent, so an order on an instrument with a finer tick was not the order you asked for, and a value below 0.00001 was sent as zero. The OKX and KuCoin keepalives now send the text ping each exchange requires instead of a WebSocket ping, and reconnect when no pong comes back. Read more: Binance Rate Limits: Batching, Pacing and Readable 429s.

zlib 1.3.1 and a bill of materials

The bundled zlib moves from 1.2.12 to 1.3.1, which corrects CVE-2022-37434, a heap over-read in inflate. The linked objects were rebuilt for Delphi 7 to Delphi 13, 32 and 64 bit, and verified on every compiler. A side fix: the zlib objects were not linked at all on Delphi 10 Seattle 64 bit, which picked the 32 bit objects and failed to compile.

Every setup now also installs sbom.cdx.json, a CycloneDX software bill of materials listing the components the library is built from with their versions and licences. Enterprise and All-Access state the versions of the customised Indy and its zlib, Core, Standard and Professional record that Indy is the one supplied with Delphi or C++ Builder. It is generated per edition at installer build time, so it always matches what you installed. Read more: zlib 1.3.1 in sgcWebSockets and sgcOpenAPI and sgcWebSockets now installs its own SBOM.

Security fixes

Beyond TLS, this release closes a number of holes. Read the list, and if one of these components is in your application, upgrade.

One more that is easy to miss: most TLS options were lost when copied from one component to another. Only the IO handler, ALPN protocols and OpenSSL options were copied, so the certificate files, password, root certificate, TLS version and VerifyCertificate stayed empty, and a component set up that way ended up not verifying anything.

Smaller additions

Also new around this release

Two pieces that ship with the library got their own articles this month: the REST server components, an HTTP server built for REST APIs with CORS, metrics, health endpoints, multi-tenancy and an OpenAPI plugin that turns one specification file into routing, validation, security and Swagger UI, and sgcProtoBuf, the code generator that turns .proto files into Delphi units. Read more: TsgcHTTPRESTServer: the New REST Server Component, REST Server + OpenAPI, Users, Multi-Tenancy and Metrics and sgcProtoBuf: From .proto Files to Delphi Units.

The .NET edition

sgcWebSockets .NET 2026.8 carries the shared half of this release. The SChannel work is all there, the version that was ignored, the settings lost on cloned connections, the unchecked renegotiation, the TLS 1.3 connection that never completed, the dropped connection that went unnoticed, the unbounded handshake and the startup race, plus the close notification before closing and client certificate verification on the server. The IOCP and EPOLL fixes, the WebSocket handshake and frame validation, the MQTT, STOMP, AMQP and HTTP/2 hardening, the OAuth2, WebAuthn, MCP and Files protocol security fixes, and the exchange changes (the Binance Spot user data stream, the paced reconnect replay, the OKX and KuCoin keepalives) are included as well. The graceful STOMP Disconnect with its receipt, and the control frame limit on the WebSocket server, are new there too.

Upgrading

2026.8 is a drop-in upgrade for existing 2026.x projects, with two things worth knowing before you build. The Binance Spot user data stream now signs its subscription, so Binance.ApiSecret has to be set alongside Binance.ApiKey. And the WebSocket server now accepts 10,000 simultaneous connections by default instead of an unlimited number, so raise MaxConnections or set it to 0 if you run above that.

Everything else is off until you ask for it: revocation checking, the version floor, strong crypto, client certificate verification, keep-alive on the IOCP and EPOLL engines, the message throttle and the MCP allowed origins list all default to the previous behaviour.

Customers with an active subscription can download the new build from the customer area, or from esegece.com/products/websockets/download.

Questions, feedback or migration help? Get in touch. You will get a reply from the people who wrote the code.