Compressione gzip e deflate nel server HTTP di sgcWebSockets | eSeGeCe Blog

Compressione gzip e deflate nel server HTTP di sgcWebSockets

· Componenti

Ogni browser del pianeta invia Accept-Encoding: gzip, deflate a ogni richiesta. Finora i server HTTP di sgcWebSockets lo ignoravano e inviavano il corpo non compresso. Il tuo HTML, il tuo CSS, il tuo JavaScript e il tuo JSON viaggiavano sulla rete a piena dimensione, anche se il client ti stava dicendo, a ogni singola richiesta, che sarebbe stato ben felice di scompattarne uno più piccolo.

sgcWebSockets 2026.7 aggiunge la compressione delle risposte ai server HTTP. Quando il client dichiara gzip o deflate, il server può ora comprimere la risposta prima di inviarla. Funziona per i file statici serviti da DocumentRoot e per le risposte che costruisci tu stesso in un gestore di evento. È disattivata per impostazione predefinita, quindi l'aggiornamento non cambia nulla finché non la attivi.

Come attivarla

La compressione si configura attraverso un unico oggetto di opzioni, HTTPCompression, sul server. Su TsgcWebSocketHTTPServer è una proprietà published, quindi puoi impostarla nell'Object Inspector in fase di design, oppure via codice:

uses
  sgcWebSocket;

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

Questa è tutta la configurazione. Con Enabled := True il server esamina l'header Accept-Encoding di ogni richiesta e, se il client supporta uno degli algoritmi configurati, comprime il corpo della risposta e imposta Content-Encoding di conseguenza. Aggiunge anche Vary: Accept-Encoding, così le cache e i proxy davanti al tuo server usano la codifica come chiave, invece di consegnare un corpo compresso con gzip a un client che non è in grado di leggerlo.

Se il client non dichiara la compressione, non succede nulla e la risposta esce esattamente come prima. La compressione non viene mai imposta a un client che non l'ha richiesta.

La proprietà Algorithms è un insieme, ed entrambi i membri sono abilitati per impostazione predefinita:

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

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

Quando il client supporta entrambi, il server preferisce gzip.

Che cosa viene compresso e che cosa no

Non tutte le risposte vale la pena comprimerle. La lista ContentTypes decide quali lo sono, e arriva con un valore predefinito sensato: text/*, application/json, application/javascript, application/xml, application/xhtml+xml e image/svg+xml. Le voci che terminano con /* corrispondono a un'intera famiglia, quindi text/* copre HTML, CSS, testo semplice e tutto il resto.

Testo, HTML, CSS, JavaScript e JSON si comprimono molto bene, perché sono pieni di token, nomi di tag e spazi ripetuti. I formati già compressi no. Un JPEG, un PNG, un'immagine WebP, un archivio ZIP, un file .gz o un video MP4 hanno già eliminato la propria ridondanza, e passarli di nuovo attraverso zlib serve solo a bruciare CPU per produrre un corpo di dimensioni pressoché identiche, a volte leggermente maggiori. Questi content type non sono nella lista, quindi vengono saltati.

Puoi sostituire la lista con la tua:

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;

Il controllo ignora qualsiasi suffisso charset, quindi una risposta con Content-Type: text/html; charset=utf-8 corrisponde comunque a text/*. Nel server c'è anche una regola di sicurezza aggiuntiva: se il corpo compresso non risulta più piccolo dell'originale, la compressione viene scartata e viene inviato il corpo in chiaro. Non puoi finire per spedire più byte di quanti ne avresti spediti senza la compressione.

Livello di compressione e dimensione minima

Level è il consueto livello zlib, da 1 a 9, e il valore predefinito è 6. Quel valore predefinito è il classico compromesso tra velocità e rapporto di compressione, ed è il valore con cui gira quasi ogni web server al mondo. Portarlo a 9 costa sensibilmente più CPU su ogni singola risposta e, per il JSON, raramente porta un vantaggio significativo: il grande guadagno rispetto al corpo non compresso si ottiene già al livello 1, e i livelli da 6 a 9 si contendono gli ultimi punti percentuali. Se il tuo server è limitato dalla CPU, scendere a 3 o 4 è un esperimento molto più interessante che salire a 9.

MinSize vale 1024 byte per impostazione predefinita, e i corpi più piccoli vengono inviati così come sono. Un flusso gzip porta con sé un proprio header e un proprio trailer, e sotto il kilobyte circa l'overhead di incapsulamento più il costo di CPU semplicemente non valgono la manciata di byte risparmiati. Le piccole conferme JSON e i redirect brevi ricadono in questa categoria, ed è giusto così.

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;

Che cosa ci guadagni

Il guadagno si vede sulla rete, non nel tuo codice. Una pagina HTML o una risposta di una API JSON sono altamente ripetitive, quindi un payload tipico di qualche decina di kilobyte di solito si comprime diverse volte prima di lasciare il server. Meno byte sulla rete significano meno round trip per consegnarli, ed è lì che il vantaggio si fa sentire davvero: sulle connessioni mobili e sui collegamenti ad alta latenza, il tempo di consegna di una risposta è dominato dal numero di round trip, non dalla larghezza di banda. Un corpo che sta in meno pacchetti semplicemente arriva prima.

Il costo è CPU sul server, una volta per risposta, ed è modesto al livello predefinito. Per un sito ricco di testo o per una API JSON, lo scambio conviene quasi sempre.

Le risposte che costruisci tu

Non devi fare nulla di speciale nei tuoi gestori di evento. Costruisci la risposta come hai sempre fatto, imposta ContentText o ContentStream e il ContentType, e il server la comprime in uscita se il client ha richiesto la compressione e il content type è nella 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;

Il passo di compressione viene eseguito dopo il ritorno del tuo gestore, quindi il Content-Length che il client vede è la lunghezza compressa, e Content-Encoding viene impostato per te. Se imposti tu stesso ContentEncoding, perché stai servendo un corpo già compresso, il server lascia la tua risposta completamente intatta.

Due tipi di risposta non vengono mai compressi, deliberatamente: i flussi Server-Sent Events (text/event-stream), che non devono essere bufferizzati, e le risposte senza corpo come 204 e 304.

File serviti da DocumentRoot

I file statici ricevono lo stesso trattamento, senza scrivere una riga di codice. Punta il server su una cartella, abilita la compressione, e i file HTML, CSS, JavaScript, SVG e JSON al suo interno vengono compressi in uscita. Le immagini e gli archivi nella stessa cartella no, perché i loro content type non sono nella 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

Il server HTTP.sys

Il server HTTP.sys di Windows, TsgcWebSocketServer_HTTPAPI, ha lo stesso oggetto di opzioni HTTPCompression, con le stesse proprietà e gli stessi valori predefiniti. C'è una differenza su cui vale la pena essere precisi: nel server HTTP.sys la proprietà è public anziché published, quindi non compare nell'Object Inspector. Devi impostarla via codice, prima di attivare il server:

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, per ora

In questa release vengono compresse solo le risposte HTTP/1.1. Le risposte HTTP/2 e HTTP/3 non vengono toccate. Se servi il tuo sito su HTTP/2, questa funzionalità per ora non cambia nulla per te. È giusto dirlo chiaramente, invece di lasciarti abilitare una proprietà e chiederti perché nulla si è ridotto.

Disponibilità

La compressione delle risposte HTTP è disponibile in sgcWebSockets 2026.7 per Delphi da 7 a 13 e C++Builder, su Win32, Win64, Linux64 e macOS. È disattivata per impostazione predefinita: dopo l'aggiornamento non cambia nulla nei tuoi server esistenti finché non imposti HTTPCompression.Enabled := True.

I clienti con un abbonamento attivo possono scaricare la nuova build dall'area clienti. Gli utenti trial possono ottenere l'installer aggiornato su esegece.com/products/websockets/download.

Domande, commenti o bisogno di aiuto per mettere a punto i content type del tuo server? Contattaci, riceverai una risposta dalle persone che hanno scritto il codice.