6. Gestione delle connessioni
La messaggistica HTTP è indipendente dal protocollo (o dai protocolli) di connessione sottostante a livello di trasporto o di sessione. HTTP presuppone soltanto un trasporto affidabile con consegna in ordine delle richieste e corrispondente consegna in ordine delle risposte. La mappatura delle strutture di richiesta e risposta HTTP sulle unità dati di un protocollo di trasporto sottostante è al di fuori dell'ambito di questa specifica.
Come descritto nella Sezione 5.2, i protocolli di connessione specifici da usare per un'interazione HTTP sono determinati dalla configurazione del client e dal target URI. Ad esempio, lo schema URI "http" (Sezione 2.7.1) indica una connessione predefinita TCP su IP, con una porta TCP predefinita 80, ma il client potrebbe essere configurato per usare un proxy tramite un'altra connessione, porta o protocollo.
Ci si aspetta che le implementazioni HTTP si occupino della gestione delle connessioni, che comprende il mantenimento dello stato delle connessioni correnti, l'instaurazione di una nuova connessione o il riutilizzo di una connessione esistente, l'elaborazione dei messaggi ricevuti su una connessione, il rilevamento dei guasti di connessione e la chiusura di ciascuna connessione. La maggior parte dei client mantiene più connessioni in parallelo, incluse più di una connessione per endpoint del server. La maggior parte dei server è progettata per mantenere migliaia di connessioni simultanee, controllando al contempo le code delle richieste per consentire un uso equo e rilevare attacchi di denial-of-service.
6.1. Connection
Il campo di intestazione "Connection" consente al mittente di indicare le opzioni di controllo desiderate per la connessione corrente. Per evitare di confondere i destinatari a valle, un proxy o gateway MUST rimuovere o sostituire qualsiasi opzione di connessione ricevuta prima di inoltrare il messaggio.
Quando per fornire informazioni di controllo per la connessione corrente o sulla connessione corrente viene usato un campo di intestazione diverso da Connection, il mittente MUST elencare il corrispondente field-name all'interno del campo di intestazione Connection. Un proxy o gateway MUST analizzare un campo di intestazione Connection ricevuto prima che un messaggio venga inoltrato e, per ciascuna connection-option in questo campo, rimuovere dal messaggio qualsiasi campo di intestazione con lo stesso nome della connection-option, quindi rimuovere il campo di intestazione Connection stesso (o sostituirlo con le proprie opzioni di connessione dell'intermediario per il messaggio inoltrato).
Pertanto, il campo di intestazione Connection fornisce un modo dichiarativo per distinguere i campi di intestazione destinati soltanto al destinatario immediato ("hop-by-hop") da quelli destinati a tutti i destinatari della catena ("end-to-end"), rendendo il messaggio autodescrittivo e consentendo di distribuire future estensioni specifiche della connessione senza timore che vengano inoltrate ciecamente da intermediari più vecchi.
Il valore del campo di intestazione Connection ha la seguente grammatica:
Connection = 1#connection-option
connection-option = token
Le opzioni di connessione non distinguono maiuscole e minuscole.
Un mittente MUST NOT inviare un'opzione di connessione corrispondente a un campo di intestazione destinato a tutti i destinatari del payload. Ad esempio, Cache-Control non è mai appropriato come opzione di connessione (Sezione 5.2 di [RFC7234]).
Le opzioni di connessione non corrispondono sempre a un campo di intestazione presente nel messaggio, poiché un campo di intestazione specifico della connessione potrebbe non essere necessario se non vi sono parametri associati a un'opzione di connessione. Al contrario, un campo di intestazione specifico della connessione ricevuto senza una corrispondente opzione di connessione di solito indica che il campo è stato inoltrato in modo improprio da un intermediario e dovrebbe essere ignorato dal destinatario.
Quando si definiscono nuove opzioni di connessione, gli autori delle specifiche dovrebbero esaminare i nomi dei campi di intestazione esistenti e assicurarsi che la nuova opzione di connessione non condivida lo stesso nome di un campo di intestazione già distribuito. Definire una nuova opzione di connessione riserva essenzialmente quel potenziale field-name per trasportare informazioni aggiuntive relative all'opzione di connessione, poiché non sarebbe saggio che i mittenti usassero quel field-name per qualsiasi altro scopo.
L'opzione di connessione "close" è definita per consentire a un mittente di segnalare che questa connessione verrà chiusa dopo il completamento della risposta. Ad esempio,
Connection: close
nei campi di intestazione della richiesta o della risposta indica che il mittente chiuderà la connessione dopo che l'attuale scambio richiesta/risposta sarà completato (Sezione 6.6).
Un client che non supporta le connessioni persistenti MUST inviare l'opzione di connessione "close" in ogni messaggio di richiesta.
Un server che non supporta le connessioni persistenti MUST inviare l'opzione di connessione "close" in ogni messaggio di risposta che non abbia un codice di stato 1xx (Informational).
6.2. Instaurazione
Descrivere come le connessioni vengano instaurate tramite i vari protocolli a livello di trasporto o di sessione esula dall'ambito di questa specifica. Ciascuna connessione si applica a un solo collegamento di trasporto.
6.3. Persistenza
HTTP/1.1 usa per impostazione predefinita le "connessioni persistenti", consentendo di trasportare più richieste e risposte su un'unica connessione. L'opzione di connessione "close" è usata per segnalare che una connessione non persisterà dopo l'attuale scambio richiesta/risposta. Le implementazioni HTTP SHOULD supportare le connessioni persistenti.
Un destinatario determina se una connessione è persistente o meno in base alla versione del protocollo e al campo di intestazione Connection (se presente) del messaggio ricevuto più di recente:
-
Se è presente l'opzione di connessione "close", la connessione non persisterà dopo la risposta corrente; altrimenti,
-
Se il protocollo ricevuto è HTTP/1.1 (o successivo), la connessione persisterà dopo la risposta corrente; altrimenti,
-
Se il protocollo ricevuto è HTTP/1.0, è presente l'opzione di connessione "keep-alive", il destinatario non è un proxy e il destinatario desidera rispettare il meccanismo "keep-alive" di HTTP/1.0, la connessione persisterà dopo la risposta corrente; in caso contrario,
-
La connessione verrà chiusa dopo la risposta corrente.
Un client MAY inviare richieste aggiuntive su una connessione persistente finché non invia o riceve un'opzione di connessione "close" oppure non riceve una risposta HTTP/1.0 priva dell'opzione di connessione "keep-alive".
Per rimanere persistente, tutti i messaggi su una connessione devono avere una lunghezza del messaggio autodefinita (cioè non definita dalla chiusura della connessione), come descritto nella Sezione 3.3. Un server MUST leggere l'intero corpo del messaggio di richiesta oppure chiudere la connessione dopo aver inviato la propria risposta, poiché altrimenti i dati rimanenti su una connessione persistente verrebbero interpretati erroneamente come la richiesta successiva. Allo stesso modo, un client MUST leggere l'intero corpo del messaggio di risposta se intende riutilizzare la stessa connessione per una richiesta successiva.
Un server proxy MUST NOT mantenere una connessione persistente con un client HTTP/1.0 (vedere la Sezione 19.7.1 di [RFC2068] per informazioni e una discussione sui problemi del campo di intestazione Keep-Alive implementato da molti client HTTP/1.0).
Vedere l'Appendice A.1.2 per ulteriori informazioni sulla retrocompatibilità con i client HTTP/1.0.
6.3.1. Nuovi tentativi delle richieste
Le connessioni possono essere chiuse in qualsiasi momento, con o senza intenzione. Le implementazioni dovrebbero prevedere la necessità di riprendersi da eventi di chiusura asincrona.
Quando una connessione in entrata viene chiusa prematuramente, un client MAY aprire una nuova connessione e ritrasmettere automaticamente una sequenza di richieste interrotta se tutte quelle richieste hanno metodi idempotenti (Sezione 4.2.2 di [RFC7231]). Un proxy MUST NOT ritentare automaticamente richieste non idempotenti.
Uno user agent MUST NOT ritentare automaticamente una richiesta con un metodo non idempotente, a meno che non disponga di qualche mezzo per sapere che la semantica della richiesta è effettivamente idempotente, indipendentemente dal metodo, oppure di qualche mezzo per rilevare che la richiesta originale non è mai stata applicata. Ad esempio, uno user agent che sa (per progettazione o configurazione) che una richiesta POST verso una data risorsa è sicura può ripetere automaticamente quella richiesta. Allo stesso modo, uno user agent progettato specificamente per operare su un repository di controllo di versione potrebbe essere in grado di riprendersi da condizioni di guasto parziale controllando la o le revisioni della risorsa di destinazione dopo una connessione fallita, ripristinando o correggendo le modifiche applicate parzialmente, e quindi ritentando automaticamente le richieste fallite.
Un client SHOULD NOT ritentare automaticamente un nuovo tentativo automatico fallito.
6.3.2. Pipelining
Un client che supporta le connessioni persistenti MAY "pipeline" le proprie richieste (cioè inviare più richieste senza attendere ciascuna risposta). Un server MAY elaborare in parallelo una sequenza di richieste in pipeline se hanno tutte metodi sicuri (Sezione 4.2.1 di [RFC7231]), ma MUST inviare le risposte corrispondenti nello stesso ordine in cui le richieste sono state ricevute.
Un client che mette le richieste in pipeline SHOULD ritentare le richieste senza risposta se la connessione si chiude prima che riceva tutte le risposte corrispondenti. Quando ritenta richieste in pipeline dopo una connessione fallita (una connessione non chiusa esplicitamente dal server nella sua ultima risposta completa), un client MUST NOT mettere richieste in pipeline immediatamente dopo l'instaurazione della connessione, poiché la prima richiesta rimanente nella pipeline precedente potrebbe aver causato una risposta di errore che può nuovamente andare perduta se più richieste vengono inviate su una connessione chiusa prematuramente (vedere il problema del TCP reset descritto nella Sezione 6.6).
I metodi idempotenti (Sezione 4.2.2 di [RFC7231]) sono importanti per il pipelining perché possono essere ritentati automaticamente dopo un guasto di connessione. Uno user agent SHOULD NOT mettere richieste in pipeline dopo un metodo non idempotente, finché non è stato ricevuto il codice di stato della risposta finale per quel metodo, a meno che lo user agent non disponga di un mezzo per rilevare e recuperare da condizioni di guasto parziale che coinvolgono la sequenza in pipeline.
Un intermediario che riceve richieste in pipeline MAY mettere quelle richieste in pipeline quando le inoltra in entrata, poiché può contare sullo o sugli user agent in uscita per determinare quali richieste possono essere messe in pipeline in modo sicuro. Se la connessione in entrata fallisce prima di ricevere una risposta, l'intermediario che fa pipelining MAY tentare di ritentare una sequenza di richieste che non hanno ancora ricevuto una risposta se le richieste hanno tutte metodi idempotenti; in caso contrario, l'intermediario che fa pipelining SHOULD inoltrare qualsiasi risposta ricevuta e quindi chiudere la o le corrispondenti connessioni in uscita, affinché lo o gli user agent in uscita possano riprendersi di conseguenza.
6.4. Concorrenza
Un client dovrebbe limitare il numero di connessioni aperte simultanee che mantiene verso un dato server.
Le revisioni precedenti di HTTP indicavano un numero specifico di connessioni come limite massimo, ma ciò si è rivelato poco pratico per molte applicazioni. Di conseguenza, questa specifica non impone un numero massimo particolare di connessioni ma, invece, incoraggia i client a essere conservativi quando aprono più connessioni.
Più connessioni sono tipicamente usate per evitare il problema di "head-of-line blocking", in cui una richiesta che richiede un'elaborazione lato server significativa e/o ha un payload di grandi dimensioni blocca le richieste successive sulla stessa connessione. Tuttavia, ciascuna connessione consuma risorse del server. Inoltre, l'uso di più connessioni può causare effetti collaterali indesiderati nelle reti congestionate.
Si noti che un server potrebbe rifiutare traffico che giudica abusivo o caratteristico di un attacco di denial-of-service, come un numero eccessivo di connessioni aperte da un singolo client.
6.5. Guasti e timeout
I server di solito hanno un certo valore di timeout oltre il quale non manterranno più una connessione inattiva. I server proxy potrebbero impostarlo a un valore più alto, poiché è probabile che il client effettui più connessioni attraverso lo stesso server proxy. L'uso di connessioni persistenti non impone requisiti sulla durata (o sull'esistenza) di questo timeout né per il client né per il server.
Un client o server che desidera andare in timeout SHOULD effettuare una chiusura graduale della connessione. Le implementazioni SHOULD monitorare costantemente le connessioni aperte in attesa di un segnale di chiusura ricevuto e rispondervi in modo appropriato, poiché la pronta chiusura di entrambi i lati di una connessione consente di recuperare le risorse di sistema allocate.
Un client, un server o un proxy MAY chiudere la connessione di trasporto in qualsiasi momento. Ad esempio, un client potrebbe aver iniziato a inviare una nuova richiesta nello stesso momento in cui il server ha deciso di chiudere la connessione "inattiva". Dal punto di vista del server, la connessione viene chiusa mentre era inattiva, ma dal punto di vista del client è in corso una richiesta.
Un server SHOULD sostenere le connessioni persistenti, quando possibile, e consentire ai meccanismi di controllo di flusso del trasporto sottostante di risolvere i sovraccarichi temporanei, anziché terminare le connessioni aspettandosi che i client ritentino. Quest'ultima tecnica può aggravare la congestione della rete.
Un client che invia un corpo del messaggio SHOULD monitorare la connessione di rete in attesa di una risposta di errore mentre sta trasmettendo la richiesta. Se il client rileva una risposta che indica che il server non desidera ricevere il corpo del messaggio e sta chiudendo la connessione, il client SHOULD cessare immediatamente la trasmissione del corpo e chiudere il proprio lato della connessione.
6.6. Chiusura
Il campo di intestazione Connection (Sezione 6.1) fornisce un'opzione di connessione "close" che un mittente SHOULD inviare quando desidera chiudere la connessione dopo l'attuale coppia richiesta/risposta.
Un client che invia un'opzione di connessione "close" MUST NOT inviare ulteriori richieste su quella connessione (dopo quella che contiene "close") e MUST chiudere la connessione dopo aver letto il messaggio di risposta finale corrispondente a questa richiesta.
Un server che riceve un'opzione di connessione "close" MUST avviare una chiusura della connessione (vedere di seguito) dopo aver inviato la risposta finale alla richiesta che conteneva "close". Il server SHOULD inviare un'opzione di connessione "close" nella sua risposta finale su quella connessione. Il server MUST NOT elaborare ulteriori richieste ricevute su quella connessione.
Un server che invia un'opzione di connessione "close" MUST avviare una chiusura della connessione (vedere di seguito) dopo aver inviato la risposta che contiene "close". Il server MUST NOT elaborare ulteriori richieste ricevute su quella connessione.
Un client che riceve un'opzione di connessione "close" MUST cessare di inviare richieste su quella connessione e chiudere la connessione dopo aver letto il messaggio di risposta contenente "close"; se erano state inviate richieste in pipeline aggiuntive sulla connessione, il client SHOULD NOT presumere che esse verranno elaborate dal server.
Se un server esegue una chiusura immediata di una connessione TCP, esiste un rischio significativo che il client non riesca a leggere l'ultima risposta HTTP. Se il server riceve dati aggiuntivi dal client su una connessione completamente chiusa, come un'altra richiesta inviata dal client prima di ricevere la risposta del server, lo stack TCP del server invierà un pacchetto di reset al client; sfortunatamente, il pacchetto di reset potrebbe cancellare i buffer di input non ancora riconosciuti del client prima che possano essere letti e interpretati dal parser HTTP del client.
Per evitare il problema del TCP reset, i server di solito chiudono una connessione per fasi. In primo luogo, il server esegue una half-close chiudendo solo il lato di scrittura della connessione di lettura/scrittura. Il server continua quindi a leggere dalla connessione finché non riceve una chiusura corrispondente dal client, oppure finché il server non è ragionevolmente certo che il proprio stack TCP abbia ricevuto il riconoscimento del client per il o i pacchetti contenenti l'ultima risposta del server. Infine, il server chiude completamente la connessione.
Non è noto se il problema del reset sia esclusivo di TCP o possa riscontrarsi anche in altri protocolli di connessione di trasporto.
6.7. Upgrade
Il campo di intestazione "Upgrade" è inteso a fornire un meccanismo semplice per passare da HTTP/1.1 a un altro protocollo sulla stessa connessione. Un client MAY inviare un elenco di protocolli nel campo di intestazione Upgrade di una richiesta per invitare il server a passare a uno o più di quei protocolli, in ordine di preferenza decrescente, prima di inviare la risposta finale. Un server MAY ignorare un campo di intestazione Upgrade ricevuto se desidera continuare a usare il protocollo corrente su quella connessione. Upgrade non può essere usato per imporre un cambio di protocollo.
Upgrade = 1#protocol
protocol = protocol-name ["/" protocol-version]
protocol-name = token
protocol-version = token
Un server che invia una risposta 101 (Switching Protocols) MUST inviare un campo di intestazione Upgrade per indicare il o i nuovi protocolli ai quali la connessione viene commutata; se vengono commutati più livelli di protocollo, il mittente MUST elencare i protocolli in ordine crescente di livello. Un server MUST NOT passare a un protocollo che non era stato indicato dal client nel campo di intestazione Upgrade della richiesta corrispondente. Un server MAY scegliere di ignorare l'ordine di preferenza indicato dal client e selezionare il o i nuovi protocolli in base ad altri fattori, come la natura della richiesta o il carico corrente sul server.
Un server che invia una risposta 426 (Upgrade Required) MUST inviare un campo di intestazione Upgrade per indicare i protocolli accettabili, in ordine di preferenza decrescente.
Un server MAY inviare un campo di intestazione Upgrade in qualsiasi altra risposta per annunciare che implementa il supporto per l'aggiornamento ai protocolli elencati, in ordine di preferenza decrescente, quando appropriato per una richiesta futura.
Il seguente è un esempio ipotetico inviato da un client:
GET /hello.txt HTTP/1.1
Host: www.example.com
Connection: upgrade
Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
Le capacità e la natura della comunicazione a livello di applicazione dopo il cambio di protocollo dipendono interamente dal o dai nuovi protocolli scelti. Tuttavia, immediatamente dopo aver inviato la risposta 101 (Switching Protocols), ci si aspetta che il server continui a rispondere alla richiesta originale come se l'avesse ricevuta nel nuovo protocollo (cioè il server ha ancora una richiesta in sospeso da soddisfare dopo che il protocollo è stato cambiato, e ci si aspetta che lo faccia senza richiedere che la richiesta venga ripetuta).
Ad esempio, se il campo di intestazione Upgrade viene ricevuto in una richiesta GET e il server decide di cambiare protocollo, esso risponde dapprima con un messaggio 101 (Switching Protocols) in HTTP/1.1 e poi fa immediatamente seguire l'equivalente, nel nuovo protocollo, di una risposta a un GET sulla risorsa di destinazione. Ciò consente di aggiornare una connessione a protocolli con la stessa semantica di HTTP senza il costo di latenza di un ulteriore round trip. Un server MUST NOT cambiare protocolli a meno che la semantica del messaggio ricevuto possa essere soddisfatta dal nuovo protocollo; una richiesta OPTIONS può essere soddisfatta da qualsiasi protocollo.
Il seguente è un esempio di risposta alla richiesta ipotetica di cui sopra:
HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: HTTP/2.0
[... data stream switches to HTTP/2.0 with an appropriate response
(as defined by new protocol) to the "GET /hello.txt" request ...]
Quando viene inviato Upgrade, il mittente MUST inviare anche un campo di intestazione Connection (Sezione 6.1) che contenga un'opzione di connessione "upgrade", per impedire che Upgrade venga accidentalmente inoltrato da intermediari che potrebbero non implementare i protocolli elencati. Un server MUST ignorare un campo di intestazione Upgrade ricevuto in una richiesta HTTP/1.0.
Un client non può iniziare a usare un protocollo aggiornato sulla connessione finché non ha inviato completamente il messaggio di richiesta (cioè il client non può cambiare il protocollo che sta inviando a metà di un messaggio). Se un server riceve sia un campo di intestazione Upgrade sia un campo di intestazione Expect con l'aspettativa "100-continue" (Sezione 5.1.1 di [RFC7231]), il server MUST inviare una risposta 100 (Continue) prima di inviare una risposta 101 (Switching Protocols).
Il campo di intestazione Upgrade si applica solo al cambio di protocollo al di sopra della connessione esistente; non può essere usato per cambiare il protocollo di connessione (trasporto) sottostante, né per spostare la comunicazione esistente su una connessione diversa. Per tali scopi, è più appropriato usare una risposta 3xx (Redirection) (Sezione 6.4 di [RFC7231]).
Questa specifica definisce solo il nome di protocollo "HTTP" per l'uso da parte della famiglia di Hypertext Transfer Protocol, come definito dalle regole di versione HTTP della Sezione 2.6 e da futuri aggiornamenti a questa specifica. Token aggiuntivi dovrebbero essere registrati presso l'IANA usando la procedura di registrazione definita nella Sezione 8.6.