sgcWebSockets 2026.8: QUIC, HTTP/3, OpenSSL estático y una revisión a fondo de TLS | Blog de eSeGeCe

sgcWebSockets 2026.8: QUIC, HTTP/3, OpenSSL estático y una revisión a fondo de TLS

· Versiones

sgcWebSockets 2026.8 es la versión más grande del año. Hay cuatro nuevos componentes de transporte, QUIC y HTTP/3 tanto en el lado del cliente como en el del servidor, OpenSSL ya se puede enlazar dentro de su ejecutable, de modo que una aplicación TLS se distribuye sin una sola DLL, y sgcHTML crece en más de treinta componentes, incorpora un dispatcher para WebBroker y DataSnap y se adapta a la pantalla de un móvil.

Por debajo, la capa TLS de SChannel se ha desmontado y reconstruido. Si utiliza la pila TLS de Windows, esta es la versión que debe instalar: TLSOptions.Version se ignoraba en silencio, se aceptaba un certificado revocado, una conexión TLS 1.3 nunca llegaba a completarse y una renegociación sustituía el certificado sin ninguna comprobación. Todo eso está corregido, y ahora están disponibles la comprobación de revocación, un mínimo de versión y la verificación del certificado de cliente en el servidor. El resto de este artículo es la visita guiada, y cada sección enlaza con el artículo que trata la función en profundidad.

QUIC y HTTP/3

Cuatro nuevos componentes llevan QUIC (RFC 9000) y HTTP/3 (RFC 9114) a Delphi y C++ Builder: TsgcQUICClient, TsgcQUICServer, TsgcHTTP3Client y TsgcHTTP3Server. Funcionan sobre el motor QUIC nativo de OpenSSL 3.5, así que no hay que desplegar ninguna pila de terceros, y obtiene TLS 1.3 integrado en el handshake, flujos multiplexados sin bloqueo de cabecera de línea, reanudación 0-RTT y migración de conexión cuando el cliente cambia de red.

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;

El cliente HTTP/3 habla todos los verbos HTTP, hace compresión de cabeceras QPACK, funciona de forma bloqueante o asíncrona, descubre un endpoint HTTP/3 mediante Alt-Svc y gestiona el 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;

Ambos requieren el paquete sgcQUIC con sgcWebSockets Enterprise. Lea más: Componentes QUIC cliente y servidor y Componentes HTTP/3 cliente y servidor.

OpenSSL dentro del ejecutable

Desplegar libcrypto y libssl junto a la aplicación siempre ha sido la parte menos agradable de distribuir un cliente TLS. A partir de 2026.8 la librería puede enlazar OpenSSL 3.5.7 de forma estática: añada una unidad a la cláusula uses de su proyecto y las DLL desaparecen. Quite la unidad y las DLL se cargan de nuevo exactamente como antes, así que la elección es un cambio de una línea en cualquiera de los dos sentidos.

uses
  sgcWebSocket, sgcWebSocket_Classes,
  sgcIdSSLOpenSSL_Static;

Cubre clientes y servidores, 32 y 64 bits, desde Delphi XE2 en adelante. Lea más: Enlazado estático de OpenSSL, se acabaron las DLL.

sgcHTML: WebBroker, DataSnap y treinta componentes nuevos

Las páginas sgcHTML ya no están ligadas al servidor de sgcWebSockets. Un nuevo componente dispatcher las sirve desde cualquier aplicación WebBroker, standalone, ISAPI, Apache o CGI, y desde servidores DataSnap, donde el puente añade actualizaciones WebSocket en vivo en el mismo puerto que sus endpoints 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

El conjunto de componentes ha crecido muchísimo en esta versión, y todo se sigue dibujando en el servidor sin ninguna librería de cliente que cargar:

Las páginas también se adaptan ahora al tamaño de la pantalla. En un móvil el menú lateral se pliega detrás de un botón y el contenido ocupa todo el ancho, en un escritorio no cambia nada. Ponga la nueva propiedad Responsive a False para volver al diseño fijo anterior. Lea más: sgcHTML sobre WebBroker y DataSnap.

La revisión a fondo de SChannel TLS

SChannel es la pila TLS de Windows, la que se obtiene sin desplegar OpenSSL en absoluto. Tenía una serie de problemas que eran invisibles desde fuera, que es la peor clase de problema. TLSOptions.Version no se leía en absoluto en Windows 11 y Windows Server 2022, pedir TLS 1.3 en Windows 10 le daba TLS 1.2 en silencio, y dejar la versión sin definir volvía a activar SSL 3.0, TLS 1.0 y TLS 1.1. Un certificado de servidor revocado se aceptaba incluso con VerifyCertificate a True, porque la cadena se construía sin pedir ningún estado de revocación. Tras una renegociación se aceptaba un nuevo certificado sin comprobar la cadena ni el nombre de host. Las conexiones que Indy abre por su cuenta, una redirección HTTP a otro host o un canal de datos FTP, volvían con una configuración vacía, así que funcionaban con la verificación desactivada y nada lo advertía.

Todo eso está corregido, la versión negociada se comprueba ahora al completarse el handshake, y con el trabajo llegaron tres nuevos grupos de opciones: comprobación de revocación, un mínimo de versión y el modo strong crypto de 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;

Los valores por defecto de revocación son deliberadamente permisivos: IgnoreRevocationOffline e IgnoreNoRevocationCheck están a True, de modo que activar la comprobación no puede romper una conexión que antes funcionaba, y el Timeout acota la obtención de CRL y OCSP para que un respondedor inaccesible no pueda bloquear el handshake. Un certificado realmente revocado se rechaza siempre.

En el lado del servidor, SChannel ya puede pedir un certificado al cliente, algo que nunca hacía antes. El certificado de cliente pasa por las mismas comprobaciones que un cliente aplica a un certificado de servidor, la cadena, las fechas y el evento OnSChannelVerifyPeer, sin la comprobación del nombre de host.

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

Otras dos correcciones pertenecen a este apartado. Un cliente que usaba SChannel nunca se enteraba de que la conexión había desaparecido cuando el otro extremo la cerraba sin notificación de cierre, como ocurre cuando se reinicia un proxy, así que OnDisconnect nunca se disparaba y el WatchDog nunca se ejecutaba. Y una conexión TLS 1.3 no llegaba a completarse nunca, porque el ticket de sesión que el servidor envía justo después del handshake dejaba al cliente esperando datos que el servidor ya había enviado. El handshake respeta ahora ConnectTimeout en lugar de cubrir solo la conexión TCP, y se envía una notificación de cierre antes de cerrar, tanto en conexiones SChannel como Apple y Android.

Límites de servidor y los motores IOCP / EPOLL

Los servidores han incorporado una serie de límites que antes eran ilimitados. El servidor WebSocket acepta 10.000 conexiones simultáneas por defecto en lugar de un número ilimitado, y limita cuántas tramas de control puede enviar un cliente cada segundo (100), de modo que no se le puede inundar con pings. El servidor HTTP/2 incorpora MaxRequestSize, y el componente DTLS MaxConnections, HandshakeTimeout e IdleTimeout, de forma que una avalancha de paquetes sueltos desde direcciones inventadas ya no llena la memoria del servidor. Todos ellos se pueden subir o poner a 0 para recuperar el comportamiento anterior.

Los motores de alto rendimiento han recibido una larga lista de reparaciones: fugas de memoria y de handles y caídas en varias rutas de limpieza, un cliente que corta la línea justo después de ser aceptado, una conexión liberada dos veces cuando el servidor se detenía a mitad de la limpieza, un uso después de liberar con los hilos de trabajo activados y un búfer perdido en cada mensaje procesado. El keep-alive HTTP no funcionaba en absoluto, así que la conexión se cerraba después de cada petición y el servidor se llenaba de sockets en TIME_WAIT. En Linux, cada petición HTTP dejaba fugas de trozos de la petición analizada, y todos los workers compartían una única cola de conexiones, de modo que uno o dos hilos hacían casi todo el trabajo.

Dos nuevas opciones lo redondean, ambas desactivadas por defecto: sondas de keep-alive y un tiempo de inactividad, para que los sockets semiabiertos ya no se acumulen, y distribución round robin de las nuevas conexiones entre los hilos de trabajo.

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

Lea más: TCPKeepAlive y KeepAliveTimeout para servidores IOCP y EPOLL. Gracias a Andrea por aportar el parche en el que se basan varias de las correcciones del motor.

Protocolos: STOMP, MQTT, AMQP y HTTP/2

STOMP ha recibido el mayor trabajo. Un Disconnect ordenado espera ahora hasta que el broker lo confirma con un recibo, de modo que no se pierde nada al cerrar. Los mensajes se entregan exactamente como los envió el broker, usando content-length para leer el cuerpo, así que puede contener saltos de línea y ceros binarios. Las tramas empaquetadas varias en un mismo mensaje WebSocket se procesan todas, una trama partida en dos mensajes se reensambla, y las tramas binarias ya no se ignoran. ACK y NACK envían las cabeceras que exige la versión negociada, y los latidos empiezan solo después de que el servidor confirme la conexión.

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

MQTT ya no construye paquetes que los brokers rechazan, un CONNECT con nombre de usuario y sin contraseña o un paquete MQTT 5 que lleva un bloque de propiedades mayor, y el cliente MQTT 5 ha dejado de leer más allá del final de un paquete: un paquete truncado entregaba a la aplicación como valores de propiedad lo que hubiera a continuación en memoria. Ahora respeta lo que le indica el broker, desde el Keep Alive devuelto en el CONNACK hasta un mensaje que llega solo con un Topic Alias. La ruta de publicación QoS 2 envía la confirmación de seguimiento correcta, descarta un mensaje que el broker rechazó en lugar de reintentarlo eternamente, y reintenta con una planificación razonable.

El cliente AMQP 1.0 está ahora protegido frente a un broker malicioso: tramas que llegan en trozos pequeños, un tamaño de cabecera inválido, mensajes anidados demasiado profundamente, un mensaje pequeño diseñado para expandirse hasta consumir una cantidad enorme de memoria. El tamaño máximo de trama por defecto para AMQP 0.9.1 y 1.0 es de 1 MB en lugar de prácticamente ilimitado. La compresión de cabeceras HTTP/2 está protegida frente a cabeceras manipuladas, la conexión rechaza una trama DATA de un stream que nunca se abrió, una avalancha de tramas PRIORITY, la reutilización de números de stream y las avalanchas de continuaciones vacías, y el servidor rechaza nombres y valores de cabecera que contengan saltos de línea o nulos, lo que evita el request smuggling. Tanto el crecimiento de memoria por streams reseteados como el "CONCURRENT STREAM limit has been exceeded" tras unas 100 peticiones están corregidos.

Servidor MCP

Las herramientas pueden exponer ahora un JSON Schema completo para su entrada, declarando arrays con items, enums, tipos enteros y nullables, objetos anidados y additionalProperties, que algunos clientes MCP exigen y sin lo cual rechazarían la herramienta. El servidor expone además los metadatos del protocolo 2025-11-25: la anotación readOnlyHint y el título de la anotación, los iconos de herramienta, el título del servidor, el sitio web y los iconos, y la declaración de soporte de tareas de tools/list.

Dos correcciones importan si ya tiene uno en marcha. El servidor escribía su id interno de conexión como un mensaje en el flujo de eventos que un cliente abre con GET, lo que clientes como VS Code GitHub Copilot reportaban como "Failed to parse message". Y la comprobación de origen solo se ejecutaba cuando la petición llevaba una cabecera que los navegadores nunca envían, así que nunca se ejecutaba en el caso que pretendía impedir: una página web visitada por el usuario podía alcanzar un servidor MCP en su propia máquina, listar sus herramientas, ejecutarlas y leer los resultados. El origen se comprueba ahora en cada petición, incluida la comprobación previa del navegador.

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

Exchanges de criptomonedas

Dos avisos de clientes han impulsado la mayor parte de esto. Una petición que falla con un estado de error HTTP informa ahora de las cabeceras con las que respondió el servidor, así que Retry-After en un 429, o los contadores de límite de tasa que devuelven los exchanges, por fin se pueden leer. Los clientes de API ya preparados lanzan EsgcHTTPAPIProtocolException, que desciende de la excepción que se lanzaba antes, de modo que los manejadores existentes siguen funcionando.

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;

Todos los clientes de exchange reenviaban su lista completa de suscripciones de una sola tacada tras reconectar, sobre una conexión de unos pocos milisegundos de vida, así que un exchange que limita los mensajes por segundo la cerraba de inmediato y el ciclo se repetía. La repetición está ahora dosificada, y en Binance se envía como tramas combinadas. Hay además un nuevo control de tasa en el lado del cliente para los mensajes que usted envía.

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 retirado los endpoints listenKey sobre los que estaba construido el user data stream de Spot, así que las actualizaciones de cuenta, órdenes y saldo llegan ahora desde la API WebSocket de Binance, a través de una segunda conexión que el componente abre por sí mismo y renueva tras una reconexión. Los eventos llegan con la misma forma, así que sus manejadores siguen funcionando, pero la nueva suscripción va firmada: ahora hay que establecer Binance.ApiSecret además de Binance.ApiKey. Binance.us y Futures siguen usando un listenKey. Los datos de mercado de USD-M Futures se sirven ahora desde dos direcciones, seleccionadas con la nueva propiedad Binance.FuturesStreamEndpoint (bfsePublic por defecto, bfseMarket para los streams de aggregate trades, mark price, kline y liquidaciones).

También corregido: los precios y tamaños de OKX se redondeaban a cinco decimales antes de enviarlos, así que una orden sobre un instrumento con un tick más fino no era la orden que usted pidió, y un valor por debajo de 0.00001 se enviaba como cero. Los keepalives de OKX y KuCoin envían ahora el ping de texto que exige cada exchange en lugar de un ping WebSocket, y reconectan cuando no llega ningún pong. Lea más: Límites de tasa de Binance: agrupación, dosificación y 429 legibles.

zlib 1.3.1 y una lista de materiales

El zlib incluido pasa de 1.2.12 a 1.3.1, lo que corrige CVE-2022-37434, una lectura fuera de rango en el heap dentro de inflate. Los objetos enlazados se han reconstruido para Delphi 7 hasta Delphi 13, 32 y 64 bits, y verificado en todos los compiladores. Una corrección colateral: los objetos de zlib no se enlazaban en absoluto en Delphi 10 Seattle de 64 bits, que tomaba los objetos de 32 bits y fallaba al compilar.

Todos los instaladores instalan ahora además sbom.cdx.json, una lista de materiales de software CycloneDX que enumera los componentes con los que está construida la librería junto con sus versiones y licencias. Enterprise y All-Access indican las versiones del Indy personalizado y de su zlib, Core, Standard y Professional registran que Indy es el que se suministra con Delphi o C++ Builder. Se genera por edición en el momento de construir el instalador, así que siempre coincide con lo que usted ha instalado. Lea más: zlib 1.3.1 en sgcWebSockets y sgcOpenAPI y sgcWebSockets ya instala su propio SBOM.

Correcciones de seguridad

Más allá de TLS, esta versión cierra unos cuantos agujeros. Lea la lista, y si alguno de estos componentes está en su aplicación, actualice.

Una más que es fácil pasar por alto: la mayoría de las opciones TLS se perdían al copiarlas de un componente a otro. Solo se copiaban el IO handler, los protocolos ALPN y las opciones de OpenSSL, así que los ficheros de certificado, la contraseña, el certificado raíz, la versión de TLS y VerifyCertificate quedaban vacíos, y un componente configurado así acababa por no verificar nada.

Añadidos menores

También nuevo alrededor de esta versión

Dos piezas que se distribuyen con la librería han tenido sus propios artículos este mes: los componentes de servidor REST, un servidor HTTP pensado para APIs REST con CORS, métricas, endpoints de salud, multi-tenencia y un plugin de OpenAPI que convierte un fichero de especificación en enrutado, validación, seguridad y Swagger UI, y sgcProtoBuf, el generador de código que convierte ficheros .proto en unidades Delphi. Lea más: TsgcHTTPRESTServer: el nuevo componente de servidor REST, Servidor REST + OpenAPI, Usuarios, multi-tenencia y métricas y sgcProtoBuf: de ficheros .proto a unidades Delphi.

La edición .NET

sgcWebSockets .NET 2026.8 incorpora la mitad compartida de esta versión. Todo el trabajo de SChannel está ahí, la versión que se ignoraba, los ajustes que se perdían en conexiones clonadas, la renegociación sin comprobar, la conexión TLS 1.3 que nunca se completaba, la conexión caída que pasaba desapercibida, el handshake sin límite y la condición de carrera al arrancar, además de la notificación de cierre antes de cerrar y la verificación del certificado de cliente en el servidor. También se incluyen las correcciones de IOCP y EPOLL, la validación del handshake y de las tramas WebSocket, el endurecimiento de MQTT, STOMP, AMQP y HTTP/2, las correcciones de seguridad de OAuth2, WebAuthn, MCP y del protocolo Files, y los cambios de los exchanges (el user data stream de Binance Spot, la repetición dosificada al reconectar, los keepalives de OKX y KuCoin). El Disconnect ordenado de STOMP con su recibo, y el límite de tramas de control en el servidor WebSocket, también son nuevos allí.

Actualización

2026.8 es una actualización directa para proyectos 2026.x existentes, con dos cosas que conviene saber antes de compilar. El user data stream de Binance Spot firma ahora su suscripción, así que hay que establecer Binance.ApiSecret junto a Binance.ApiKey. Y el servidor WebSocket acepta ahora 10.000 conexiones simultáneas por defecto en lugar de un número ilimitado, así que suba MaxConnections o póngalo a 0 si trabaja por encima de esa cifra.

Todo lo demás está desactivado hasta que usted lo pida: la comprobación de revocación, el mínimo de versión, strong crypto, la verificación del certificado de cliente, el keep-alive en los motores IOCP y EPOLL, el control de tasa de mensajes y la lista de orígenes permitidos de MCP mantienen por defecto el comportamiento anterior.

Los clientes con una suscripción activa pueden descargar la nueva compilación desde el área de clientes, o desde esegece.com/products/websockets/download.

¿Preguntas, comentarios o ayuda con la migración? Póngase en contacto. Recibirá respuesta de las personas que escribieron el código.