Compresión gzip y deflate en el servidor HTTP de sgcWebSockets | Blog de eSeGeCe

Compresión gzip y deflate en el servidor HTTP de sgcWebSockets

· Componentes

Todos los navegadores del planeta envían Accept-Encoding: gzip, deflate en cada petición. Hasta ahora, los servidores HTTP de sgcWebSockets lo ignoraban y enviaban el cuerpo sin comprimir. Tu HTML, tu CSS, tu JavaScript y tu JSON salían por el cable a tamaño completo, aunque el cliente te estuviera diciendo, en cada una de las peticiones, que con mucho gusto descomprimiría uno más pequeño.

sgcWebSockets 2026.7 añade compresión de las respuestas a los servidores HTTP. Cuando el cliente anuncia gzip o deflate, el servidor ya puede comprimir la respuesta antes de enviarla. Funciona para los archivos estáticos servidos desde DocumentRoot y para las respuestas que construyes tú mismo en un manejador de eventos. Está desactivada por defecto, así que actualizar no cambia nada hasta que la habilitas.

Cómo activarla

La compresión se configura a través de un único objeto de opciones, HTTPCompression, en el servidor. En TsgcWebSocketHTTPServer es una propiedad publicada, así que puedes establecerla en el Inspector de Objetos en tiempo de diseño, o por código:

uses
  sgcWebSocket;

begin
  sgcWebSocketHTTPServer1.HTTPCompression.Enabled := True;
  sgcWebSocketHTTPServer1.Active := True;
end;

Esa es toda la configuración. Con Enabled := True el servidor mira la cabecera Accept-Encoding de cada petición y, si el cliente admite uno de los algoritmos configurados, comprime el cuerpo de la respuesta y establece Content-Encoding en consecuencia. También añade Vary: Accept-Encoding, para que las cachés y los proxies que hay delante de tu servidor indexen por la codificación en lugar de entregar un cuerpo comprimido con gzip a un cliente que no puede leerlo.

Si el cliente no anuncia compresión, no ocurre nada y la respuesta sale exactamente igual que antes. La compresión nunca se fuerza sobre un cliente que no la ha pedido.

La propiedad Algorithms es un conjunto, y los dos miembros están habilitados por defecto:

sgcWebSocketHTTPServer1.HTTPCompression.Algorithms := [caGZip, caDeflate];

// or gzip only, if you prefer to keep it to a single well-understood encoding
sgcWebSocketHTTPServer1.HTTPCompression.Algorithms := [caGZip];

Cuando el cliente admite ambos, el servidor prefiere gzip.

Qué se comprime y qué no

No todas las respuestas merecen ser comprimidas. La lista ContentTypes decide cuáles sí, y viene con un valor por defecto razonable: text/*, application/json, application/javascript, application/xml, application/xhtml+xml e image/svg+xml. Las entradas que acaban en /* abarcan toda una familia, así que text/* cubre HTML, CSS, texto plano y todo lo demás.

El texto, el HTML, el CSS, el JavaScript y el JSON comprimen muy bien, porque están llenos de tokens repetidos, nombres de etiquetas y espacios en blanco. Los formatos que ya vienen comprimidos, no. Un JPEG, un PNG, una imagen WebP, un archivo ZIP, un archivo .gz o un vídeo MP4 ya tienen exprimida su redundancia, y pasarlos otra vez por zlib solo quema CPU para producir un cuerpo de un tamaño más o menos igual, a veces un poco mayor. Esos tipos de contenido no están en la lista, así que se omiten.

Puedes sustituir la lista por la tuya:

uses
  sgcWebSocket, sgcWebSocket_Classes;

begin
  with sgcWebSocketHTTPServer1.HTTPCompression do
  begin
    ContentTypes.Clear;
    ContentTypes.Add('text/*');                 // html, css, plain text
    ContentTypes.Add('application/json');       // your API replies
    ContentTypes.Add('application/javascript');
    ContentTypes.Add('image/svg+xml');
    ContentTypes.Add('application/x-ndjson');   // whatever else you serve

    Enabled := True;
  end;

  sgcWebSocketHTTPServer1.Active := True;
end;

La comprobación ignora cualquier sufijo de charset, así que una respuesta con Content-Type: text/html; charset=utf-8 sigue coincidiendo con text/*. Hay además una regla de doble seguridad en el servidor: si el cuerpo comprimido no sale más pequeño que el original, la compresión se descarta y se envía el cuerpo plano. No puedes acabar enviando más bytes de los que habrías enviado sin ella.

Nivel de compresión y tamaño mínimo

Level es el nivel habitual de zlib, de 1 a 9, y su valor por defecto es 6. Ese valor por defecto es el compromiso estándar entre velocidad y ratio, y es el que usa prácticamente cualquier servidor web del mundo. Subirlo a 9 cuesta bastante más CPU en cada una de las respuestas y, para JSON, rara vez te aporta mucho más: la gran ganancia frente al cuerpo sin comprimir ya se consigue en el nivel 1, y los niveles del 6 al 9 se pelean por el último puñado de puntos porcentuales. Si tu servidor está limitado por CPU, bajar a 3 o 4 es un experimento mucho más interesante que subir a 9.

MinSize vale 1024 bytes por defecto, y los cuerpos más pequeños que eso se envían tal cual. Un flujo gzip lleva su propia cabecera y su propio cierre y, por debajo de un kilobyte más o menos, la sobrecarga del formato más el coste de CPU sencillamente no compensa el puñado de bytes que ahorras. Las confirmaciones JSON diminutas y las redirecciones cortas caen en este saco, y así debe ser.

with sgcWebSocketHTTPServer1.HTTPCompression do
begin
  Level   := 6;      // 1..9, default 6
  MinSize := 1024;   // bytes, default 1024, smaller bodies are sent as-is
  Enabled := True;
end;

Qué ganas con ello

La ganancia aparece en el cable, no en tu código. Una página HTML o la respuesta de una API JSON es muy repetitiva, así que una carga típica de unas pocas decenas de kilobytes normalmente se comprime varias veces antes de salir del servidor. Menos bytes en el cable significan menos idas y vueltas para entregarlos, y ahí es donde de verdad compensa: en conexiones móviles y en enlaces de alta latencia, el tiempo de entrega de una respuesta lo domina el número de idas y vueltas, no el ancho de banda. Un cuerpo que cabe en menos paquetes sencillamente llega antes.

El coste es CPU en el servidor, una vez por respuesta, y es pequeño con el nivel por defecto. Para un sitio con mucho texto o para una API JSON, el intercambio casi siempre merece la pena.

Las respuestas que construyes tú mismo

No tienes que hacer nada especial en tus manejadores de eventos. Construye la respuesta como siempre, establece ContentText o ContentStream y el ContentType, y el servidor la comprime al salir si el cliente ha pedido compresión y el tipo de contenido está en la lista:

procedure TForm1.sgcWebSocketHTTPServer1CommandGet(aConnection: TsgcWSConnection;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
  if ARequestInfo.Document = '/api/orders' then
  begin
    AResponseInfo.ContentType := 'application/json';
    AResponseInfo.ContentText := GetOrdersAsJSON;  // your payload, plain text
    // nothing else to do. If the client sent Accept-Encoding: gzip and the
    // body is at least MinSize bytes, it goes out gzipped.
  end;
end;

El paso de compresión se ejecuta después de que tu manejador retorne, así que el Content-Length que ve el cliente es la longitud comprimida, y Content-Encoding se establece por ti. Si estableces ContentEncoding tú mismo, porque estás sirviendo un cuerpo que ya viene comprimido, el servidor deja tu respuesta completamente intacta.

Hay dos tipos de respuesta que deliberadamente nunca se comprimen: los flujos de Server-Sent Events (text/event-stream), que no deben almacenarse en búfer, y las respuestas sin cuerpo, como 204 y 304.

Archivos servidos desde DocumentRoot

Los archivos estáticos reciben el mismo trato, sin nada de código. Apunta el servidor a una carpeta, habilita la compresión, y los archivos HTML, CSS, JavaScript, SVG y JSON que hay dentro se comprimen al salir. Las imágenes y los archivos comprimidos de esa misma carpeta no, porque sus tipos de contenido no están en la lista.

sgcWebSocketHTTPServer1.HTTPUploadFiles.DocumentRoot := 'C:\www';
sgcWebSocketHTTPServer1.HTTPCompression.Enabled := True;
sgcWebSocketHTTPServer1.Active := True;

// GET /index.html   -> compressed, text/html is in ContentTypes
// GET /app.js       -> compressed
// GET /logo.png     -> not compressed, image/png is not in the list
// GET /favicon.ico  -> not compressed

El servidor HTTP.sys

El servidor HTTP.sys de Windows, TsgcWebSocketServer_HTTPAPI, tiene el mismo objeto de opciones HTTPCompression, con las mismas propiedades y los mismos valores por defecto. Hay una diferencia que conviene precisar: en el servidor HTTP.sys la propiedad es pública en lugar de publicada, así que no aparece en el Inspector de Objetos. Tienes que establecerla por código, antes de activar el servidor:

uses
  sgcWebSocket_Server_HTTPAPI;

begin
  sgcWebSocketServer_HTTPAPI1.HTTPCompression.Enabled := True;
  sgcWebSocketServer_HTTPAPI1.HTTPCompression.Level   := 6;
  sgcWebSocketServer_HTTPAPI1.HTTPCompression.MinSize := 1024;

  sgcWebSocketServer_HTTPAPI1.Active := True;
end;

Solo HTTP/1.1, por ahora

En esta versión solo se comprimen las respuestas HTTP/1.1. Las respuestas HTTP/2 y HTTP/3 no se tocan. Si sirves tu sitio sobre HTTP/2, esta funcionalidad todavía no cambia nada para ti. Merece la pena dejarlo claro, en lugar de que habilites una propiedad y te preguntes por qué nada ha encogido.

Disponibilidad

La compresión de las respuestas HTTP está disponible en sgcWebSockets 2026.7 para Delphi 7 a 13 y C++Builder, en Win32, Win64, Linux64 y macOS. Viene desactivada por defecto: nada de tus servidores actuales cambia al actualizar hasta que estableces HTTPCompression.Enabled := True.

Los clientes con una suscripción activa pueden descargar la nueva versión desde el área de clientes. Los usuarios de la versión de prueba pueden obtener el instalador actualizado en esegece.com/products/websockets/download.

¿Preguntas, comentarios o ayuda para ajustar los tipos de contenido de tu propio servidor? Ponte en contacto, recibirás una respuesta de las personas que escribieron el código.