sgcWebSockets 2026.7: Clustering, Message History, async / await and Hardened Clients | eSeGeCe Blog

sgcWebSockets 2026.7: Clustering, Message History, async / await and Hardened Clients

· Releases
sgcWebSockets 2026.7: Clustering, Message History, async / await and Hardened Clients

sgcWebSockets 2026.7 is about running in production. You can now spread a WebSocket application across several servers and keep channels, pub/sub and presence working across all of them. Channels can remember their recent messages, so a client that drops and comes back gets whatever it missed. Clients reconnect with exponential backoff and jitter instead of hammering the server in lockstep, the send queue can be bounded so one slow consumer cannot exhaust server memory, and the HTTP client gained a set of protections against redirects that leak credentials, downgraded TLS and responses with no size limit.

On top of that there is an async / await layer for Delphi, gzip and deflate on the HTTP servers, the new HTTP QUERY method, identity verification for end-to-end encryption, OAuth2 on the MCP client, and a long list of fixes. This post is the guided tour. Each section links to the article that covers the feature in depth.

Clustering across several servers

The new TsgcWSCluster component links several sgcWebSockets servers into one logical server. A message published on node A reaches subscribers connected to node B, and presence returns the roster of the whole cluster instead of just the local node. Two backplanes are available: a built-in mesh, where the nodes talk to each other directly and you install nothing extra, and Redis Pub/Sub for larger deployments (Enterprise).

uses
  sgcWebSocket, sgcWebSocket_Protocols, sgcWebSocket_Cluster;

oCluster := TsgcWSCluster.Create(nil);
oCluster.EngineType  := clusterMesh;          // no extra infrastructure
oCluster.ClusterPort := 5410;                 // this node's mesh listener
oCluster.Peers.Add('192.168.1.101:5410');     // the other nodes
oCluster.Attach(oProtocol);                   // cluster this protocol's pub/sub
oCluster.Start;

Read more: Scale WebSocket Servers Across Multiple Nodes.

Message history and reconnect recovery

The sgc protocol server can now keep the last N messages of each channel, optionally with a time-to-live. When a client reconnects and resubscribes, it sends the offset of the last message it actually saw, and the server replays everything published while it was away. Nothing changes in your client code beyond the resubscribe you already do, and the whole thing is off unless you enable it.

oProtocol.History.Enabled    := True;
oProtocol.History.Size       := 1000;   // last 1000 messages per channel
oProtocol.History.TTLSeconds := 3600;   // optional: also expire after 1 hour

Read more: Message History and Reconnect Recovery.

async / await, and a Connect that really waits

The new sgcBase_AsyncAwait unit brings tasks and futures to the library. You can await an HTTP request, a WebSocket connection or an AI chat completion without wiring a single event handler, chain the result with ThenProc and OnError, and cancel a task in a way that actually aborts the underlying request.

In the same spirit, Connect now blocks until the connection is fully established, not merely started, and Disconnect waits until the socket is really closed. That closes a race that used to bite during quick reconnects. When a connect or a send fails, the reason is available in the new LastError property, and there is a Connect overload that hands the error string straight back to you, so you can diagnose a failure without subscribing to OnError.

var
  vError: string;
begin
  if sgcWebSocketClient1.Connect(10000) then
    sgcWebSocketClient1.WriteData('hello')
  else
    ShowMessage('Connect failed: ' + sgcWebSocketClient1.LastError);

  // or get the reason back directly from the call
  if not sgcWebSocketClient1.Connect(vError, 10000) then
    ShowMessage('Connect failed: ' + vError);
end;

Read more: async / await in Delphi.

Hardening the HTTP client

A redirect is the easiest way to lose a token. If a server answers your authenticated request with a 302 to a different host, the client happily replays the Authorization and Cookie headers there. 2026.7 adds StripAuthOnCrossHostRedirect to drop them on a cross-site redirect and NoInsecureRedirect to refuse a redirect that downgrades HTTPS to plain HTTP.

Responses can now be bounded too, with MaxChunkSize for a single piece of a chunked response and MaxResponseSize for the total. RejectPublicSuffixCookies throws away cookies claimed for overly broad domains such as "com" or "co.uk". For uploads there is Expect: 100-continue, which asks the server's permission before sending a large body, and a chunked request body for streaming an upload whose size you do not know yet.

oHTTP.StripAuthOnCrossHostRedirect := True;
oHTTP.NoInsecureRedirect           := True;
oHTTP.MaxResponseSize              := 10 * 1024 * 1024;  // 10 MB, 0 = unlimited
oHTTP.MaxChunkSize                 := 1024 * 1024;       // 1 MB per chunk
oHTTP.RejectPublicSuffixCookies    := True;

All of these switches are off by default, and none of them require you to reach for the low-level HTTP object: they are published on the ready-made API clients as well (AI/LLM, crypto exchanges, OAuth2, Google Cloud, WhatsApp, AWS SQS, WebPush). The redirect and size protections are also on the HTTP/2 client, and HTTP/3 gains the response-size limit. Read more: Hardening the HTTP Client.

Retries, backoff and jitter

The WatchDog used to reconnect on a fixed interval, which means a thousand clients that lose the server all come back at the same instant. It now supports exponential backoff with a multiplier, a ceiling and a random jitter, so the fleet spreads itself out. The fixed interval remains the default, so nothing changes until you ask for it.

The HTTP client gained a retry policy of its own: retry on a configurable list of status codes, wait longer each time, and honour the server's Retry-After hint when it sends one. The OpenAI, Anthropic and Gemini clients use it too.

sgcWebSocketClient1.WatchDog.Backoff           := wdbExponential;
sgcWebSocketClient1.WatchDog.BackoffMultiplier := 2.0;   // double every attempt
sgcWebSocketClient1.WatchDog.MaxInterval       := 60;    // seconds, the ceiling
sgcWebSocketClient1.WatchDog.Jitter            := 0.2;   // up to 20% random spread

Read more: Exponential Backoff, Jitter and Retry-After.

Backpressure on the send queue

When a client reads slower than you write, the queued messages pile up in server memory. You can now cap the number of pending messages per connection with QueueOptions.MaxQueueSize and choose what happens when the cap is reached: drop the oldest message, drop the newest one, or disconnect the connection. The new OnSendBufferFull event fires before anything is dropped, so you can log it, count it, or veto the drop for a connection you cannot afford to truncate. The queue is unlimited by default.

oServer.QueueOptions.MaxQueueSize   := 1000;
oServer.QueueOptions.OverflowPolicy := qopDropOldest;  // or qopDropNewest, qopDisconnect

Read more: Send-Queue Limits for Slow WebSocket Clients.

gzip and deflate on the HTTP server

The HTTP servers can now compress their replies whenever the client advertises Accept-Encoding. It applies to files served from DocumentRoot and to responses you build yourself in OnCommandGet, it skips bodies below a minimum size and content types that would not get smaller, and it is available on the HTTP.sys server as well. Compression is off by default, and only HTTP/1.1 replies are compressed for now.

sgcWebSocketHTTPServer1.HTTPCompression.Enabled := True;
sgcWebSocketHTTPServer1.HTTPCompression.Level   := 6;     // 1..9
sgcWebSocketHTTPServer1.HTTPCompression.MinSize := 1024;  // bytes

Read more: gzip and deflate Compression on the HTTP Server.

The HTTP QUERY method

QUERY is a new IETF HTTP method that sends a search in the request body like a POST, but stays safe and cacheable like a GET, so your query is no longer squeezed into a URL with a length limit. 2026.7 implements it on the HTTP/1.x, HTTP/2, HTTP/3 and REST clients, and the servers and the proxy handle it.

vResult := oClient.Query('https://api.example.org/contacts', oQuery);

Read more: The New HTTP QUERY Method in Delphi.

E2EE identity verification

End-to-end encryption keeps the payload private, but on its own it does not tell you who is on the other end. A server or relay in the middle can hand each side its own key and read everything. In 2026.7 each side can sign its encryption key with a long-term identity key, and the other side verifies that signature, so a silent key swap is detected instead of accepted.

It works for one-to-one and for group chats, adds events to approve or pin a peer's identity and to warn you when a peer's identity key changes, and it is off by default and fully backward compatible with 2026.6 peers.

E2EE.E2EE_Options.Identity.Enabled    := True;
E2EE.E2EE_Options.Identity.PrivateKey := LoadIdentityPrivateKey;  // PEM
E2EE.E2EE_Options.Identity.PublicKey  := LoadIdentityPublicKey;   // PEM
E2EE.OnE2EEVerifyPeerIdentity := OnE2EEVerifyPeerIdentityEvent;
E2EE.OnE2EEKeyChange          := OnE2EEKeyChangeEvent;

Read more: E2EE Identity Verification.

OAuth2 for the MCP client, and the Identity Assertion grant

The MCP client can now authenticate with OAuth2 instead of a static API key. It fetches the token itself, caches it until it expires and refreshes it, and when OAuth2 is enabled it takes priority over the API key. Alongside it, the OAuth2 client gains a new grant type, the Identity Assertion Authorization Grant, which chains an identity from one domain to another: the client runs the whole multi-step exchange for you and raises events so you can follow each step.

MCPClient.MCPOptions.AuthenticationOptions.OAuth2.Enabled := True;
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.GrantType := auth2ClientCredentials;
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.TokenURL := 'https://auth.example.com/oauth2/token';
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.ClientId := 'YOUR_CLIENT_ID';
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.ClientSecret := 'YOUR_CLIENT_SECRET';

Read more: OAuth2 for the MCP Client.

Crypto feeds: resubscribe and rate limiting

Until now, when a market-data socket dropped and the WatchDog brought it back, the socket was up but you were subscribed to nothing until you replayed every subscription yourself. The exchange clients can now do it for you: set Resubscribe := True and your streams are restored after a reconnect. It covers Binance, Kraken, Coinbase and about fourteen other exchanges, plus XTB's session-based feed. It is False by default.

The REST API clients gain an optional client-side rate limiter, so a burst of requests does not get you temporarily banned by an exchange. Cap how many requests you send in a time window, and choose whether an excess request waits or is rejected. Off by default.

oBinance.RateLimit.Enabled     := True;
oBinance.RateLimit.IntervalMs  := 60000;   // one minute window
oBinance.RateLimit.MaxRequests := 1000;
oBinance.RateLimit.Behavior    := rlbWait;

Read more: Resubscribe and the Client-Side Rate Limiter.

Smaller additions

Fixes and hardening

2026.7 carries a long list of fixes. Rather than repeat all of them, here is what they add up to.

Memory safety and parser hardening. Reads past the end of the buffer were fixed in the MQTT 5 client (a broker claiming more data than it sent), the AMQP 1.0 client (UUID decoding), the STUN/TURN parser (including an infinite loop and IPv6 addresses), the WebRTC SCTP data channel and the raw-TCP frame scanner. The UDP client and server, including DTLS, no longer free a socket twice or leave it dangling on disconnect. Internal caps were added where a misbehaving peer could otherwise grow memory without bound: the MQTT pending-message queue, the per-connection subscriptions of the sgc protocol, the WAMP server's pending calls, the trailer lines of a chunked response and the nesting depth of AMQP field tables.

HTTP/2. A decompressed-size cap (64 MB by default) closes an out-of-memory hole with "zip bomb" responses, the response-size limit can no longer be bypassed by the last part of a response, and a failed request now fails immediately instead of waiting out the whole timeout.

Crypto and randomness. The WebAuthn server, the OAuth2 authorization server and the built-in HTTP session ids now use a cryptographically secure random generator, challenges are single-use, and secrets are compared in constant time (client secret, JWT signature, HTTP API Basic password). A JWT with a valid signature but a bad claim is now rejected instead of accepted. With OpenSSL StrictVerify enabled, the certificate is also checked against the host name, so a valid certificate issued for a different host is rejected. Connections to an IP address and existing settings are unaffected.

Server-Sent Events. A streamed response that delivered several events in one read, or an event split across two reads, lost all but the first. This is fixed, which restores token-by-token streaming for the AI/LLM clients. OpenAI streaming chat also failed to set the stream flag, so it now streams like the other providers.

Exchange clients. Kraken request ids are now strictly increasing, BitMEX sends its expiry in seconds instead of milliseconds, MEXC signs the encoded parameters it actually sends, Cryptorobotics no longer swaps two values in by-hash calls, Deribit refreshes its login token before it expires, and the Forex client reports the error it used to swallow while reading the account id.

Lazarus and Free Pascal. Three compilation fixes: the JSON unit's interfaces now use the right calling convention per platform (they failed to compile on Linux), the Forex client no longer calls a Delphi-only function to read the number format, and the post-quantum key exchange unit no longer depends on size_t.

There is more in history.txt, including the gRPC client (concurrent calls could receive each other's replies, and a send failure was reported as success), the OpenAPI server (spec loading is now thread-safe and your handlers no longer run while an internal lock is held), a server connection and socket leak on every failed login, a crash on OpenAI replies with no "message" field such as those from Ollama and LM Studio, a fixed WriteAndWaitData on the WinHTTP client, and secret redaction in the HTTP client log so enabling logging no longer writes tokens to disk in clear text.

The .NET edition

sgcWebSockets .NET 2026.7 focuses on the HTTP client. It ships the same set of protections as the Delphi edition: credential stripping on a cross-site redirect, a block on an HTTPS to HTTP downgrade, per-chunk and total response size caps, public-suffix cookie rejection, Expect: 100-continue and chunked request bodies. They are available on the HTTP client components and on the ready-made API clients (the AI/LLM clients, OAuth2, Google Cloud, WhatsApp, AWS SQS, WebPush), and the same redirect and size protections are on the HTTP/2 client. The redirect handling and cookie expiry fixes are included as well. Everything is off by default.

Upgrading

2026.7 is a drop-in upgrade for existing 2026.x projects. Every new feature in this release is off by default: clustering, message history, backoff, the send-queue cap, HTTP compression, the client protections, E2EE identity verification and the exchange resubscribe all have to be switched on explicitly, so installing the new build changes no behaviour until you opt in.

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.