Invia un’unica risposta corretta, scrivi il corpo progressivamente e termina il messaggio normalmente. Questa guida per il Terminale del Mac mostra come trasmettere un documento sintetico completo senza trattare ogni blocco come una risposta separata né usare 206 Partial Content.

L’obiettivo è servire un’unica pagina HTML destinata all’hosting pubblico, quindi verificare che il client ne riceva tutto il contenuto. La dimostrazione viene eseguita localmente e non pubblica nulla. Servono Terminale, curl e una versione LTS supportata di Node.js, disponibile nella pagina ufficiale per scaricare Node.js.

Perché i blocchi non sono risposte parziali

Il significato di una risposta e le modalità con cui viene trasmessa rispondono a domande diverse. Per una normale GET riuscita, 200 OK indica che il recupero è avvenuto correttamente. 206 Partial Content indica che il server soddisfa una richiesta per un intervallo trasferendo parti selezionate di una rappresentazione. Suddividere la trasmissione in più scritture non crea una richiesta per un intervallo. RFC 9110: semantica HTTP, 206 Partial Content.

Per esempio, una guida sintetica composta da un’introduzione, una descrizione e un paragrafo conclusivo può essere ricevuta progressivamente pur restando un unico documento. Invia una sola volta le intestazioni della risposta, seguite dalle sezioni del corpo nell’ordine previsto. Non anteporre una nuova riga di stato HTTP a ogni sezione.

La codifica di trasferimento chunked di HTTP/1.1 incapsula i byte del corpo in blocchi preceduti dalla loro lunghezza esadecimale e termina con un blocco di dimensione zero e la riga vuota finale obbligatoria. Questi byte di inquadramento delimitano la trasmissione; non fanno parte del contenuto dell’articolo. Il mittente non deve combinare Content-Length con Transfer-Encoding. RFC 9112: codifica di trasferimento chunked, Content-Length.

La regola pratica è lasciare che sia la libreria HTTP a inquadrare il messaggio. L’applicazione fornisce il contenuto del documento e segnala quando è terminato.

Quale meccanismo di trasmissione progressiva usare?

Il meccanismo supportato dipende dalla versione HTTP. È possibile trasmettere progressivamente un corpo completo in tutte e tre le versioni seguenti, ma il relativo inquadramento varia.

Versione HTTP Modalità di trasmissione del contenuto del corpo Meccanismo di completamento
HTTP/1.1 con codifica chunked Blocchi preceduti dalla lunghezza Blocco finale di dimensione zero e sezione dei trailer completata
HTTP/2 Frame DATA END_STREAM sul frame conclusivo, compreso il completamento dell’eventuale blocco di intestazioni
HTTP/3 Frame DATA Completamento della risposta e chiusura della direzione di invio dello stream

HTTP/2 vieta esplicitamente la codifica di trasferimento chunked di HTTP/1.1. HTTP/3 non definisce codifiche di trasferimento e vieta l’intestazione Transfer-Encoding. Lascia che il server implementi l’inquadramento previsto dal protocollo, anziché forzare Transfer-Encoding: chunked su ogni connessione. RFC 9113: scambi di messaggi HTTP, RFC 9114: inquadramento dei messaggi HTTP.

Per un documento breve già assemblato, di solito la soluzione più semplice è inviarlo normalmente. Le scritture progressive sono utili quando il contenuto viene generato nel tempo. L’esempio seguente distanzia intenzionalmente tre scritture per consentirti di esaminarne il comportamento; i ritardi sono a scopo illustrativo e non costituiscono una raccomandazione per la pubblicazione.

Avviare sul Mac un documento completo trasmesso in streaming

Apri Terminale e verifica i prerequisiti:

node --version
curl --version

Se node non è disponibile, installa una versione LTS supportata dalla pagina di download di Node.js, quindi apri una nuova finestra del Terminale. Il programma usa solo moduli integrati e non richiede pacchetti npm.

Incolla l’intero blocco seguente nel Terminale. Avvia un server HTTP/1.1 all’indirizzo dimostrativo http://127.0.0.1:8080/document. Le tre stringhe formano insieme un’unica pagina sintetica completa.

node <<'JS'
const http = require('node:http');

const parts = [
  '<!doctype html>\n<html lang="en"><head><meta charset="utf-8">' +
    '<title>Paper Shapes</title></head><body><main>\n' +
    '<h1>Paper Shapes</h1>\n',
  '<p>A circle, a square, and a triangle make this sample collection.</p>\n',
  '<p>End of the paper shapes guide.</p>\n</main></body></html>\n'
];

const server = http.createServer((req, res) => {
  res.on('error', error => console.error(error.message));
  if (req.method !== 'GET' || req.url !== '/document') {
    res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end('No sample document at this path.\n');
    return;
  }

  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
  let index = 0;
  const timer = setInterval(() => {
    if (res.destroyed) {
      clearInterval(timer);
      return;
    }
    res.write(parts[index++]);
    if (index === parts.length) {
      clearInterval(timer);
      res.end();
    }
  }, 300);
  res.on('close', () => clearInterval(timer));
});

server.on('error', error => {
  console.error(error.message);
  process.exitCode = 1;
});
server.listen(8080, '127.0.0.1', () => {
  console.log('Sample: http://127.0.0.1:8080/document');
});
JS

L’applicazione chiama writeHead() una volta, write() tre volte e end() una volta. La documentazione di Node descrive le scritture successive come parti dello stesso corpo della risposta e richiede end() per completare ogni risposta. È Node a gestire l’inquadramento HTTP: non aggiungere alle stringhe le lunghezze dei blocchi né un blocco finale di dimensione zero. Documentazione HTTP di Node.js.

È una dimostrazione circoscritta con tre stringhe brevi. Per un produttore di dati continuo o di grandi dimensioni, rispetta la gestione della contropressione: quando write() restituisce false, sospendi la produzione finché non si verifica drain, gestendo anche la chiusura e gli errori. Documentazione Node.js sulla scrittura delle risposte.

Lascia aperta questa finestra del Terminale. Se viene segnalato EADDRINUSE, la porta 8080 è occupata: scegline un’altra disponibile nel server e modifica la porta in ogni comando del client riportato di seguito. Al termine, interrompi la dimostrazione con Control-C.

Esaminare il contenuto decodificato e l’inquadramento HTTP dei blocchi

In una seconda finestra del Terminale, richiedi il documento:

curl --http1.1 --include --no-buffer --max-time 5 \
  http://127.0.0.1:8080/document

Attendi una sola risposta 200 OK, un Content-Type pari a text/html; charset=utf-8 e il contenuto assemblato della pagina. In questo esempio HTTP/1.1, la risposta dovrebbe usare Transfer-Encoding: chunked. Normalmente curl decodifica l’inquadramento del trasferimento, perciò il suo output standard mostra il documento anziché le lunghezze esadecimali dei blocchi.

Per esaminare l’inquadramento, ripeti la richiesta con --raw:

curl --http1.1 --include --no-buffer --raw --max-time 5 \
  http://127.0.0.1:8080/document

La documentazione di curl descrive --raw come un’opzione che disattiva la decodifica interna della codifica del contenuto e di trasferimento. --no-buffer disattiva il buffering dell’output di curl, mentre --max-time 5 limita la durata dell’operazione. Queste opzioni rendono il comando un breve strumento diagnostico, anziché un comando che resta in attesa indefinitamente. Manuale di curl.

Nell’output grezzo, le righe delle dimensioni delimitano il contenuto, seguito dal blocco finale di dimensione zero. Senza quel terminatore, il corpo chunked di HTTP/1.1 è incompleto. Con un inquadramento a lunghezza fissa, la ricezione di un numero di byte inferiore alla lunghezza dichiarata indica che il messaggio è incompleto. RFC 9112: gestione dei messaggi incompleti.

Usa l’output grezzo solo per esaminare il trasporto. Non copiarne le righe delle dimensioni in un file della pagina e non trattarle come sezioni del documento.

Ricevere l’intero documento prima di elaborarlo

Se il passaggio successivo richiede la pagina completa, consuma l’intero corpo prima di analizzarlo, salvarlo o accettarlo in altro modo. WHATWG definisce il corpo di una risposta come uno stream; il metodo text() lo legge interamente e restituisce una stringa, generando un rifiuto se il consumo del corpo non riesce. La sola ricezione delle intestazioni della risposta non prova che il corpo sia terminato. Fetch Standard WHATWG: mixin Body, Elaborazione delle risposte.

Mentre il server è in esecuzione, incolla questo client completo nella seconda finestra del Terminale:

node --input-type=module <<'JS'
try {
  const response = await fetch('http://127.0.0.1:8080/document', {
    signal: AbortSignal.timeout(5000)
  });
  if (response.status !== 200) {
    throw new Error(`Expected 200; received ${response.status}`);
  }

  const html = await response.text();
  const marker = 'End of the paper shapes guide.';
  if (!html.includes(marker) || !html.endsWith('</html>\n')) {
    throw new Error('Expected sample ending is missing');
  }

  console.log(html);
  console.log('Complete sample document received.');
} catch (error) {
  console.error('Document not accepted:', error.message);
  process.exitCode = 1;
}
JS

Node fornisce un’implementazione di fetch() compatibile con i browser. Il suo AbortSignal.timeout() crea un segnale che interrompe l’operazione dopo il numero di millisecondi specificato; qui, cinque secondi sono un limite illustrativo per questo piccolo esempio locale. Documentazione sugli oggetti globali di Node.js, Documentazione sul timeout di AbortSignal.

Il client verifica due aspetti diversi: il completamento riuscito della lettura del corpo e la presenza della terminazione di esempio prevista. Il secondo controllo è una diagnostica specifica dell’applicazione. HTTP può trasferire correttamente un documento che l’applicazione ha accidentalmente accorciato prima di chiamare end(). Un marcatore di completamento aiuta a rilevare questo errore specifico, ma non convalida ogni paragrafo né costituisce una convalida HTML generale.

Questo client conserva l’intera pagina in memoria. Per documenti molto grandi, scegli un’elaborazione incrementale con un formato documentale e regole di completamento espliciti. Non presumere che ogni lettura contenga un paragrafo completo o una risposta autonoma con un significato proprio.

Diagnosticare il completamento prima di modificare il codice di stato

Segui questa sequenza per individuare il problema. Separa il completamento del trasporto dal contenuto previsto dall’applicazione, così un download apparentemente riuscito non nasconde una parte finale omessa.

Osservazione Passaggio successivo
Il comando locale non riesce a connettersi Verifica che il server sia in esecuzione e che la porta corrisponda.
Curl va in timeout Controlla che il produttore termini e chiami end(); conserva l’output dell’errore.
Il trasferimento termina ma il controllo sulla parte finale non riesce Esamina il contenuto generato e il punto in cui l’applicazione termina la risposta.
L’intero documento arriva tutto insieme Confronta l’output grezzo con quello decodificato; non dedurre che vi siano più risposte in base ai tempi di visualizzazione.
Una normale richiesta della pagina completa riceve 206 Verifica se è stata applicata per errore la gestione degli intervalli; la trasmissione progressiva, da sola, non lo giustifica.

I controlli di completamento sono raccomandazioni ricavate dal protocollo e dal comportamento di Fetch, non una funzione HTTP separata. Se l’operazione di lettura del corpo non riesce, il client qui sopra segnala l’errore e termina con esito negativo, anziché mostrare un messaggio di successo per il contenuto ricevuto fino a quel momento.

Cosa comporta questo per una pagina pubblica e Google Search?

Mantieni il contenuto utile del documento nella risposta HTML stessa. Google spiega che la scansione e l’analisi funzionano bene per le pagine la cui risposta HTML contiene tutti i contenuti e raccomanda il rendering lato server o il prerendering, perché non tutti i bot eseguono JavaScript. L’esempio segue questo approccio: il testo arriva direttamente nel corpo del documento. Google Search Central: comprendere i fondamenti della SEO JavaScript.

La documentazione per i crawler di Google afferma che una risposta 200 passa i contenuti ricevuti alla fase di elaborazione successiva; non ne garantisce l’indicizzazione. La documentazione citata non specifica una dimensione dei blocchi o un ritardo particolare che assicuri l’indicizzazione di una pagina. Considera lo streaming una modalità di trasmissione e verifica separatamente che il contenuto sia completo. Google: effetti dei codici di stato HTTP sui crawler di Google.

Quando adatti questa dimostrazione all’hosting pubblico, ripeti i controlli sull’endpoint effettivo del documento. Verifica lo stato di successo, consuma l’intero corpo e confronta il contenuto finale con quello previsto per la pagina. Mantieni il controllo di accettazione indipendente dal numero di scritture, frame o aggiornamenti visibili usati per trasmettere il documento.

Fonti