sgcWebSockets 2026.8: QUIC, HTTP/3, OpenSSL statico e una revisione completa di TLS | Blog eSeGeCe

sgcWebSockets 2026.8: QUIC, HTTP/3, OpenSSL statico e una revisione completa di TLS

· Versioni

sgcWebSockets 2026.8 è la versione più ampia dell'anno. Ci sono quattro nuovi componenti di trasporto, QUIC e HTTP/3 sia lato client sia lato server, OpenSSL può ora essere collegato all'interno del tuo eseguibile, così un'applicazione TLS viene distribuita senza una sola DLL, e sgcHTML cresce di oltre trenta componenti, guadagna un dispatcher WebBroker e DataSnap e si adatta allo schermo di un telefono.

Sotto la superficie, il livello TLS SChannel è stato smontato e ricostruito. Se usi lo stack TLS di Windows, questa è la versione da installare: TLSOptions.Version veniva ignorato silenziosamente, un certificato revocato veniva accettato, una connessione TLS 1.3 non si completava mai e una rinegoziazione sostituiva il certificato senza alcun controllo. Tutto questo è stato corretto, e ora sono disponibili il controllo della revoca, un limite minimo di versione e la verifica del certificato client sul server. Il resto di questo articolo è la visita guidata, e ogni sezione rimanda all'articolo che tratta la funzionalità in dettaglio.

QUIC e HTTP/3

Quattro nuovi componenti portano QUIC (RFC 9000) e HTTP/3 (RFC 9114) su Delphi e C++ Builder: TsgcQUICClient, TsgcQUICServer, TsgcHTTP3Client e TsgcHTTP3Server. Funzionano sul motore QUIC nativo di OpenSSL 3.5, quindi non c'è alcuno stack di terze parti da distribuire, e ottieni TLS 1.3 integrato nell'handshake, stream multiplexati senza head of line blocking, ripresa 0-RTT e migrazione della connessione quando il client cambia rete.

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;

Il client HTTP/3 parla ogni verbo HTTP, esegue la compressione degli header QPACK, funziona in modo bloccante o asincrono, individua un endpoint HTTP/3 tramite Alt-Svc e gestisce il 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;

Entrambi richiedono il pacchetto sgcQUIC con sgcWebSockets Enterprise. Approfondisci: Componenti QUIC Client e Server e Componenti HTTP/3 Client e Server.

OpenSSL dentro l'eseguibile

Distribuire libcrypto e libssl accanto all'applicazione è sempre stata la parte meno piacevole della distribuzione di un client TLS. Da 2026.8 la libreria può collegare OpenSSL 3.5.7 staticamente: aggiungi una sola unit alla clausola uses del tuo progetto e le DLL spariscono. Rimuovi la unit e le DLL vengono caricate di nuovo esattamente come prima, quindi la scelta è una modifica di una riga in entrambe le direzioni.

uses
  sgcWebSocket, sgcWebSocket_Classes,
  sgcIdSSLOpenSSL_Static;

Copre client e server, 32 e 64 bit, da Delphi XE2 in poi. Approfondisci: Collegamento statico di OpenSSL, niente più DLL.

sgcHTML: WebBroker, DataSnap e trenta nuovi componenti

Le pagine sgcHTML non sono più legate al server sgcWebSockets. Un nuovo componente dispatcher le serve da qualsiasi applicazione WebBroker, standalone, ISAPI, Apache o CGI, e dai server DataSnap, dove il bridge aggiunge aggiornamenti WebSocket in tempo reale sulla stessa porta dei tuoi endpoint REST.

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

Il set di componenti è cresciuto molto in questa versione, e tutto viene ancora disegnato sul server senza alcuna libreria lato client da caricare:

Ora anche le pagine si adattano alla dimensione dello schermo. Su un telefono il menu laterale si ripiega dietro un pulsante e il contenuto usa tutta la larghezza, su un desktop non cambia nulla. Imposta la nuova proprietà Responsive a False per il layout fisso precedente. Approfondisci: sgcHTML su WebBroker e DataSnap.

La revisione completa di SChannel TLS

SChannel è lo stack TLS di Windows, quello che ottieni senza distribuire OpenSSL. Aveva una serie di problemi invisibili dall'esterno, che è il tipo peggiore. TLSOptions.Version non veniva letto affatto su Windows 11 e Windows Server 2022, richiedere TLS 1.3 su Windows 10 restituiva silenziosamente TLS 1.2, e lasciare la versione non definita riattivava SSL 3.0, TLS 1.0 e TLS 1.1. Un certificato server revocato veniva accettato anche con VerifyCertificate impostato a True, perché la catena veniva costruita senza richiedere alcuno stato di revoca. Dopo una rinegoziazione un nuovo certificato veniva accettato senza alcun controllo della catena o del nome host. Le connessioni che Indy apre lateralmente, un redirect HTTP verso un altro host o un canale dati FTP, tornavano con una configurazione vuota, quindi funzionavano con la verifica disattivata e nulla lo segnalava.

Tutto questo è stato corretto, la versione negoziata viene ora verificata al completamento dell'handshake, e con questo lavoro sono arrivati tre nuovi gruppi di opzioni: controllo della revoca, un limite minimo di versione e la crittografia forte di Windows.

// 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;

I valori predefiniti della revoca sono volutamente tolleranti: IgnoreRevocationOffline e IgnoreNoRevocationCheck sono True, quindi attivare il controllo non può interrompere una connessione che prima funzionava, e il Timeout limita il recupero di CRL e OCSP in modo che un responder irraggiungibile non possa bloccare l'handshake. Un certificato effettivamente revocato viene sempre rifiutato.

Lato server, SChannel può ora richiedere un certificato al client, cosa che prima non faceva mai. Il certificato client passa attraverso gli stessi controlli che un client applica a un certificato server, la catena, le date e l'evento OnSChannelVerifyPeer, senza il controllo del nome host.

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

Altre due correzioni appartengono a questa sezione. Un client che usava SChannel non si accorgeva mai che la connessione era caduta quando l'altro capo la chiudeva senza una notifica di chiusura, come accade quando un proxy si riavvia, quindi OnDisconnect non veniva mai generato e il WatchDog non entrava mai in funzione. E una connessione TLS 1.3 non si completava affatto, perché il session ticket che il server invia subito dopo l'handshake lasciava il client in attesa di dati che il server aveva già inviato. L'handshake ora rispetta ConnectTimeout invece di coprire solo la connessione TCP, e una notifica di chiusura viene inviata prima di chiudere, sulle connessioni SChannel, Apple e Android allo stesso modo.

Limiti del server e i motori IOCP / EPOLL

I server hanno guadagnato una serie di limiti che prima erano illimitati. Il server WebSocket accetta 10.000 connessioni simultanee per impostazione predefinita invece di un numero illimitato, e limita quanti frame di controllo un client può inviare al secondo (100), così non può essere inondato di ping. Il server HTTP/2 ha guadagnato MaxRequestSize, e il componente DTLS MaxConnections, HandshakeTimeout e IdleTimeout, così un'ondata di singoli pacchetti da indirizzi inventati non riempie più la memoria del server. Ognuno di essi può essere aumentato o impostato a 0 per il comportamento precedente.

I motori ad alte prestazioni hanno ricevuto un lungo elenco di riparazioni: perdite di memoria e di handle e crash su diversi percorsi di pulizia, un client che chiude la linea subito dopo essere stato accettato, una connessione rilasciata due volte quando il server veniva arrestato durante la pulizia, un use after free con i thread di lavoro abilitati, e un buffer perso a ogni messaggio elaborato. Il keep-alive HTTP non funzionava affatto, quindi la connessione veniva chiusa dopo ogni richiesta e il server si riempiva di socket in TIME_WAIT. Su Linux, ogni richiesta HTTP perdeva parti della richiesta analizzata, e tutti i worker condividevano un'unica coda di connessioni, quindi uno o due thread svolgevano quasi tutto il lavoro.

Due nuove opzioni completano il quadro, entrambe disattivate per impostazione predefinita: i probe keep-alive e un timeout di inattività, così i socket half-open non si accumulano più, e la distribuzione round robin delle nuove connessioni tra i thread di lavoro.

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

Approfondisci: TCPKeepAlive e KeepAliveTimeout per i server IOCP ed EPOLL. Grazie ad Andrea per aver contribuito la patch su cui si basano diverse correzioni dei motori.

Protocolli: STOMP, MQTT, AMQP e HTTP/2

STOMP ha ricevuto il maggior numero di interventi. Un Disconnect graceful ora attende finché il broker non lo conferma con una ricevuta, così non si perde nulla in chiusura. I messaggi vengono consegnati esattamente come li ha inviati il broker, usando content-length per leggere il corpo, così può contenere interruzioni di riga e zeri binari. I frame impacchettati in numero multiplo in un messaggio WebSocket vengono elaborati tutti, un frame diviso su due messaggi viene ricomposto, e i frame binari non vengono più ignorati. ACK e NACK inviano gli header che la versione negoziata richiede, e gli heart-beat partono solo dopo che il server ha confermato la connessione.

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

MQTT non costruisce più pacchetti che i broker rifiutano, un CONNECT con un nome utente e senza password oppure un pacchetto MQTT 5 che trasporta un blocco di proprietà più grande, e il client MQTT 5 ha smesso di leggere oltre la fine di un pacchetto: un pacchetto troncato consegnava all'applicazione come valori di proprietà tutto ciò che seguiva in memoria. Ora rispetta quello che gli comunica il broker, dal Keep Alive restituito nel CONNACK fino a un messaggio che arriva con il solo Topic Alias. Il percorso di pubblicazione QoS 2 invia la conferma successiva corretta, scarta un messaggio rifiutato dal broker invece di riprovare all'infinito, e riprova con una cadenza ragionevole.

Il client AMQP 1.0 è ora protetto contro un broker malevolo: frame che arrivano in piccoli pezzi, una dimensione di header non valida, messaggi annidati troppo in profondità, un messaggio piccolo costruito per espandersi in un'enorme quantità di memoria. La dimensione massima predefinita dei frame per AMQP 0.9.1 e 1.0 è 1 MB invece che praticamente illimitata. La compressione degli header HTTP/2 è protetta contro header costruiti ad arte, la connessione rifiuta un frame DATA per uno stream mai aperto, un'ondata di frame PRIORITY, il riuso dei numeri di stream e le ondate di continuation vuote, e il server rifiuta nomi e valori di header contenenti interruzioni di riga o null, il che previene il request smuggling. Sono stati corretti sia la crescita della memoria dovuta agli stream resettati sia il "CONCURRENT STREAM limit has been exceeded" dopo circa 100 richieste.

Server MCP

Gli strumenti possono ora esporre uno JSON Schema completo per il loro input, dichiarando array con items, enum, tipi interi e nullable, oggetti annidati e additionalProperties, che alcuni client MCP richiedono e altrimenti rifiuterebbero. Il server espone anche i metadati del protocollo 2025-11-25: l'annotazione readOnlyHint e il titolo dell'annotazione, le icone degli strumenti, il titolo del server, il sito web e le icone, e la dichiarazione di supporto dei task in tools/list.

Due correzioni sono importanti se ne stai già usando uno. Il server scriveva il proprio id di connessione interno come messaggio sullo stream di eventi che un client apre con GET, cosa che client come VS Code GitHub Copilot segnalavano come "Failed to parse message". E il controllo dell'origine veniva eseguito soltanto quando la richiesta trasportava un header che i browser non inviano mai, quindi non veniva mai eseguito proprio nel caso che avrebbe dovuto bloccare: una pagina web visitata dall'utente poteva raggiungere un server MCP sulla sua stessa macchina, elencarne gli strumenti, eseguirli e leggere i risultati. L'origine viene ora controllata a ogni richiesta, compreso il controllo preliminare del browser.

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

Exchange di criptovalute

Due segnalazioni dei clienti hanno guidato la maggior parte di questo lavoro. Una richiesta che fallisce con uno stato di errore HTTP riporta ora gli header con cui il server ha risposto, così Retry-After su un 429, oppure i contatori dei rate limit che gli exchange restituiscono, possono finalmente essere letti. I client API preconfezionati sollevano EsgcHTTPAPIProtocolException, che discende dall'eccezione sollevata in precedenza, quindi i gestori esistenti continuano a funzionare.

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;

Ogni client di exchange rinviava tutta la sua lista di sottoscrizioni in un'unica raffica dopo la riconnessione, su una connessione vecchia di pochi millisecondi, così un exchange che limita i messaggi al secondo la chiudeva immediatamente e il ciclo si ripeteva. Il replay è ora cadenzato, e su Binance viene inviato come frame combinati. C'è anche un nuovo throttle lato client sui messaggi che invii.

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 ha dismesso gli endpoint listenKey su cui era costruito lo user data stream Spot, quindi gli aggiornamenti di account, ordini e saldo arrivano ora dalla WebSocket API di Binance, su una seconda connessione che il componente apre da solo e rinnova dopo una riconnessione. Gli eventi arrivano nella stessa forma, quindi i tuoi gestori continuano a funzionare, ma la nuova sottoscrizione è firmata: Binance.ApiSecret deve ora essere impostato oltre a Binance.ApiKey. Binance.us e Futures usano ancora una listenKey. I dati di mercato USD-M Futures sono ora serviti da due indirizzi, selezionati con la nuova proprietà Binance.FuturesStreamEndpoint (bfsePublic per impostazione predefinita, bfseMarket per gli stream di aggregate trade, mark price, kline e liquidazioni).

Corretto inoltre: i prezzi e le quantità di OKX venivano arrotondati a cinque decimali prima dell'invio, quindi un ordine su uno strumento con un tick più fine non era l'ordine che avevi richiesto, e un valore inferiore a 0,00001 veniva inviato come zero. I keepalive di OKX e KuCoin inviano ora il ping testuale richiesto da ciascun exchange invece di un ping WebSocket, e si riconnettono quando non arriva alcun pong. Approfondisci: Rate limit di Binance: batching, cadenza e 429 leggibili.

zlib 1.3.1 e una distinta base

La zlib inclusa passa da 1.2.12 a 1.3.1, che corregge CVE-2022-37434, una lettura oltre l'heap in inflate. Gli object collegati sono stati ricompilati per Delphi 7 fino a Delphi 13, 32 e 64 bit, e verificati su ogni compilatore. Una correzione collaterale: gli object zlib non venivano collegati affatto su Delphi 10 Seattle 64 bit, che sceglieva gli object a 32 bit e non riusciva a compilare.

Ogni setup installa ora anche sbom.cdx.json, una distinta base software CycloneDX che elenca i componenti da cui è costruita la libreria con le loro versioni e licenze. Enterprise e All-Access indicano le versioni di Indy personalizzato e della sua zlib, Core, Standard e Professional registrano che Indy è quello fornito con Delphi o C++ Builder. Viene generata per edizione al momento della compilazione dell'installer, così corrisponde sempre a ciò che hai installato. Approfondisci: zlib 1.3.1 in sgcWebSockets e sgcOpenAPI e sgcWebSockets installa ora il proprio SBOM.

Correzioni di sicurezza

Oltre a TLS, questa versione chiude una serie di falle. Leggi l'elenco, e se uno di questi componenti è presente nella tua applicazione, aggiorna.

Un'altra facile da non notare: la maggior parte delle opzioni TLS andava persa nella copia da un componente a un altro. Venivano copiati solo l'IO handler, i protocolli ALPN e le opzioni OpenSSL, quindi i file di certificato, la password, il certificato radice, la versione TLS e VerifyCertificate restavano vuoti, e un componente configurato in quel modo finiva per non verificare nulla.

Aggiunte minori

Altre novità intorno a questa versione

Due elementi distribuiti con la libreria hanno avuto un articolo dedicato questo mese: i componenti server REST, un server HTTP costruito per le API REST con CORS, metriche, endpoint di health, multi-tenancy e un plugin OpenAPI che trasforma un singolo file di specifica in routing, validazione, sicurezza e Swagger UI, e sgcProtoBuf, il generatore di codice che trasforma i file .proto in unit Delphi. Approfondisci: TsgcHTTPRESTServer: il nuovo componente server REST, Server REST + OpenAPI, Utenti, multi-tenancy e metriche e sgcProtoBuf: dai file .proto alle unit Delphi.

L'edizione .NET

sgcWebSockets .NET 2026.8 porta con sé la metà condivisa di questa versione. Il lavoro su SChannel c'è tutto, la versione che veniva ignorata, le impostazioni perse sulle connessioni clonate, la rinegoziazione non verificata, la connessione TLS 1.3 che non si completava mai, la connessione caduta che passava inosservata, l'handshake senza limiti e la race condition in avvio, oltre alla notifica di chiusura prima di chiudere e alla verifica del certificato client sul server. Sono incluse anche le correzioni IOCP ed EPOLL, la validazione dell'handshake e dei frame WebSocket, l'irrobustimento di MQTT, STOMP, AMQP e HTTP/2, le correzioni di sicurezza di OAuth2, WebAuthn, MCP e del protocollo Files, e le modifiche agli exchange (lo user data stream Spot di Binance, il replay cadenzato in riconnessione, i keepalive di OKX e KuCoin). Anche il Disconnect graceful di STOMP con la sua ricevuta, e il limite sui frame di controllo nel server WebSocket, sono nuovi anche lì.

Aggiornamento

2026.8 è un aggiornamento drop-in per i progetti 2026.x esistenti, con due cose da sapere prima di compilare. Lo user data stream Spot di Binance ora firma la propria sottoscrizione, quindi Binance.ApiSecret deve essere impostato insieme a Binance.ApiKey. E il server WebSocket accetta ora 10.000 connessioni simultanee per impostazione predefinita invece di un numero illimitato, quindi aumenta MaxConnections o impostalo a 0 se superi quel valore.

Tutto il resto è disattivato finché non lo richiedi: il controllo della revoca, il limite minimo di versione, la crittografia forte, la verifica del certificato client, il keep-alive sui motori IOCP ed EPOLL, il throttle dei messaggi e la lista delle origini consentite di MCP hanno tutti come impostazione predefinita il comportamento precedente.

I clienti con un abbonamento attivo possono scaricare la nuova build dall'area clienti, oppure da esegece.com/products/websockets/download.

Domande, commenti o aiuto per la migrazione? Contattaci. Riceverai una risposta dalle persone che hanno scritto il codice.