9. Gestione delle connessioni (Connection Management)
La messaggistica HTTP è indipendente dai protocolli di connessione del livello di trasporto o di sessione sottostanti. HTTP presuppone soltanto un trasporto affidabile con consegna ordinata delle richieste e la corrispondente consegna ordinata 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 7.3 di [HTTP], i protocolli di connessione specifici da usare per un'interazione HTTP sono determinati dalla configurazione del client e dall'URI di destinazione. Ad esempio, lo schema URI "http" (sezione 4.2.1 di [HTTP]) 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, lo stabilimento 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 ogni connessione. La maggior parte dei client mantiene più connessioni in parallelo, incluse più di una connessione per endpoint server. La maggior parte dei server è progettata per mantenere migliaia di connessioni simultanee, controllando al contempo le code di richieste per consentire un uso equo e rilevare attacchi di negazione del servizio.
9.1. Stabilimento (Establishment)
Descrivere come vengono stabilite le connessioni tramite i vari protocolli di livello di trasporto o di sessione esula dall'ambito di questa specifica. Ogni connessione HTTP corrisponde a una connessione di trasporto sottostante.
9.2. Associazione di una risposta a una richiesta (Associating a Response to a Request)
HTTP/1.1 non include un identificatore di richiesta per associare un determinato messaggio di richiesta ai suoi uno o più messaggi di risposta corrispondenti. Pertanto, si affida all'ordine di arrivo delle risposte, che deve corrispondere esattamente all'ordine in cui le richieste vengono effettuate sulla stessa connessione. Più di un messaggio di risposta per richiesta si verifica solo quando una o più risposte informative (1xx; vedere la sezione 15.2 di [HTTP]) precedono una risposta finale alla stessa richiesta.
Un client che ha più di una richiesta in sospeso su una connessione deve (MUST) mantenere un elenco delle richieste in sospeso nell'ordine di invio e deve (MUST) associare ogni messaggio di risposta ricevuto su quella connessione alla prima richiesta in sospeso che non ha ancora ricevuto una risposta finale (non 1xx).
Se un client riceve dati su una connessione che non ha richieste in sospeso, il client non deve (MUST NOT) considerare tali dati come una risposta valida; il client dovrebbe (SHOULD) chiudere la connessione, poiché la delimitazione dei messaggi è ormai ambigua, a meno che i dati non consistano unicamente in uno o più CRLF (che possono essere scartati secondo la sezione 2.2).
9.3. Persistenza (Persistence)
HTTP/1.1 usa per impostazione predefinita le "connessioni persistenti", consentendo di trasportare più richieste e risposte su un'unica connessione. Le implementazioni HTTP dovrebbero (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 (sezione 7.6.1 di [HTTP]) nel messaggio ricevuto più di recente, se presente:
- Se è presente l'opzione di connessione "close" (sezione 9.6), 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 oppure il messaggio è una risposta, e il destinatario desidera rispettare il meccanismo "keep-alive" di HTTP/1.0, la connessione persisterà dopo la risposta corrente; altrimenti,
- La connessione verrà chiusa dopo la risposta corrente.
Un client che non supporta le connessioni persistenti deve (MUST) inviare l'opzione di connessione "close" in ogni messaggio di richiesta.
Un server che non supporta le connessioni persistenti deve (MUST) inviare l'opzione di connessione "close" in ogni messaggio di risposta che non abbia un codice di stato 1xx (Informational).
Un client può (MAY) inviare richieste aggiuntive su una connessione persistente finché non invia o riceve un'opzione di connessione "close", oppure finché 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 6. Un server deve (MUST) leggere l'intero corpo del messaggio di richiesta oppure chiudere la connessione dopo aver inviato la propria risposta; in caso contrario, i dati rimanenti su una connessione persistente verrebbero interpretati erroneamente come la richiesta successiva. Analogamente, un client deve (MUST) leggere l'intero corpo del messaggio di risposta se intende riutilizzare la stessa connessione per una richiesta successiva.
Un server proxy non deve (MUST NOT) mantenere una connessione persistente con un client HTTP/1.0 (per informazioni e discussioni sui problemi del campo di intestazione Keep-Alive implementato da molti client HTTP/1.0, vedere l'appendice C.2.2).
Per ulteriori informazioni sulla compatibilità retroattiva con i client HTTP/1.0, vedere l'appendice C.2.2.
9.3.1. Nuovi tentativi delle richieste (Retrying Requests)
Le connessioni possono essere chiuse in qualsiasi momento, intenzionalmente o meno. Le implementazioni dovrebbero prevedere la necessità di ripristinarsi da eventi di chiusura asincroni. Le condizioni alle quali un client può ritentare automaticamente una sequenza di richieste in sospeso sono definite nella sezione 9.2.2 di [HTTP].
9.3.2. Pipelining
Un client che supporta le connessioni persistenti può (MAY) "mettere in pipeline" le proprie richieste (cioè inviare più richieste senza attendere ogni risposta). Un server può (MAY) elaborare in parallelo una sequenza di richieste messe in pipeline se tutte utilizzano metodi sicuri (sezione 9.2.1 di [HTTP]), ma deve (MUST) inviare le risposte corrispondenti nello stesso ordine in cui le richieste sono state ricevute.
Un client che mette in pipeline le richieste dovrebbe (SHOULD) ritentare le richieste senza risposta se la connessione si chiude prima che riceva tutte le risposte corrispondenti. Quando ritenta richieste messe in pipeline dopo una connessione fallita (una connessione non chiusa esplicitamente dal server nella sua ultima risposta completa), un client non deve (MUST NOT) mettere in pipeline immediatamente dopo lo stabilimento della connessione, poiché la prima richiesta rimanente nella pipeline precedente potrebbe aver causato una risposta di errore che può andare nuovamente perduta se più richieste vengono inviate su una connessione chiusa prematuramente (vedere il problema del reset TCP descritto nella sezione 9.6).
I metodi idempotenti (sezione 9.2.2 di [HTTP]) sono importanti per il pipelining perché possono essere ritentati automaticamente dopo un guasto della connessione. Un user agent non dovrebbe (SHOULD NOT) mettere in pipeline richieste dopo un metodo non idempotente, finché non è stato ricevuto il codice di stato della risposta finale per quel metodo, a meno che l'user agent non disponga di un mezzo per rilevare e ripristinarsi da condizioni di guasto parziale che coinvolgono la sequenza messa in pipeline.
Un intermediario che riceve richieste messe in pipeline può (MAY) mettere in pipeline tali richieste quando le inoltra verso l'interno, poiché può fare affidamento sugli user agent in uscita per determinare quali richieste possono essere messe in pipeline in modo sicuro. Se la connessione in ingresso fallisce prima di ricevere una risposta, l'intermediario che usa il pipelining può (MAY) tentare di ritentare una sequenza di richieste che non hanno ancora ricevuto una risposta, se le richieste utilizzano tutte metodi idempotenti; in caso contrario, l'intermediario che usa il pipelining dovrebbe (SHOULD) inoltrare le risposte ricevute e poi chiudere le corrispondenti connessioni in uscita, in modo che gli user agent in uscita possano ripristinarsi di conseguenza.
9.4. Concorrenza (Concurrency)
Un client dovrebbe limitare il numero di connessioni aperte simultaneamente che mantiene verso un determinato 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 particolare numero massimo di connessioni, ma incoraggia invece i client a essere prudenti quando aprono più connessioni.
Le connessioni multiple sono tipicamente usate per evitare il problema del "blocco in testa alla coda" (head-of-line blocking), in cui una richiesta che richiede un'elaborazione lato server significativa e/o trasferisce contenuti molto grandi bloccherebbe le richieste successive sulla stessa connessione. Tuttavia, ogni connessione consuma risorse del server.
Inoltre, l'uso di più connessioni può causare effetti collaterali indesiderati in reti congestionate. L'uso di un numero maggiore di connessioni può anche causare effetti collaterali in reti altrimenti non congestionate, perché il loro comportamento di invio aggregato e inizialmente sincronizzato può causare una congestione che non sarebbe presente se fossero state usate meno connessioni parallele.
Si noti che un server potrebbe rifiutare traffico che ritiene abusivo o caratteristico di un attacco di negazione del servizio, come un numero eccessivo di connessioni aperte da un singolo client.
9.5. Guasti e timeout (Failures and Timeouts)
I server hanno di solito un valore di timeout oltre il quale non mantengono 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é al client né al server.
Un client o un server che desidera andare in timeout dovrebbe (SHOULD) effettuare una chiusura graduale della connessione. Le implementazioni dovrebbero (SHOULD) monitorare costantemente le connessioni aperte per rilevare un segnale di chiusura ricevuto e rispondervi in modo appropriato, poiché la chiusura tempestiva di entrambi i lati di una connessione consente di recuperare le risorse di sistema allocate.
Un client, un server o un proxy può (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 dovrebbe (SHOULD) mantenere le connessioni persistenti, quando possibile, e lasciare che i meccanismi di controllo di flusso del trasporto sottostante risolvano i sovraccarichi temporanei, anziché terminare le connessioni aspettandosi che i client ritentino. Quest'ultima tecnica può aggravare la congestione della rete o il carico del server.
Un client che invia un corpo del messaggio dovrebbe (SHOULD) monitorare la connessione di rete per una risposta di errore mentre trasmette la richiesta. Se il client vede una risposta che indica che il server non desidera ricevere il corpo del messaggio e sta chiudendo la connessione, il client dovrebbe (SHOULD) cessare immediatamente la trasmissione del corpo e chiudere il proprio lato della connessione.
9.6. Chiusura (Tear-down)
L'opzione di connessione "close" è definita come un segnale che il mittente chiuderà questa connessione dopo il completamento della risposta. Un mittente dovrebbe (SHOULD) inviare un campo di intestazione Connection (sezione 7.6.1 di [HTTP]) contenente l'opzione di connessione "close" quando intende chiudere una connessione. Ad esempio,
Connection: close
come campo di intestazione di una richiesta indica che questa è l'ultima richiesta che il client invierà su questa connessione, mentre in una risposta lo stesso campo indica che il server chiuderà questa connessione dopo che il messaggio di risposta è completo.
Si noti che il nome di campo "Close" è riservato, poiché l'uso di quel nome come campo di intestazione potrebbe entrare in conflitto con l'opzione di connessione "close".
Un client che invia un'opzione di connessione "close" non deve (MUST NOT) inviare ulteriori richieste su quella connessione (dopo quella che contiene "close") e deve (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" deve (MUST) avviare la chiusura della connessione (vedere sotto) dopo aver inviato la risposta finale alla richiesta che conteneva l'opzione di connessione "close". Il server dovrebbe (SHOULD) inviare un'opzione di connessione "close" nella sua risposta finale su quella connessione. Il server non deve (MUST NOT) elaborare ulteriori richieste ricevute su quella connessione.
Un server che invia un'opzione di connessione "close" deve (MUST) avviare la chiusura della connessione (vedere sotto) dopo aver inviato la risposta che contiene l'opzione di connessione "close". Il server non deve (MUST NOT) elaborare ulteriori richieste ricevute su quella connessione.
Un client che riceve un'opzione di connessione "close" deve (MUST) cessare di inviare richieste su quella connessione e chiudere la connessione dopo aver letto il messaggio di risposta che contiene l'opzione di connessione "close"; se erano state inviate richieste aggiuntive messe in pipeline sulla connessione, il client non dovrebbe (SHOULD NOT) presumere che vengano 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 confermati del client prima che possano essere letti e interpretati dal parser HTTP del client.
Per evitare il problema del reset TCP, i server in genere chiudono una connessione per fasi. Innanzitutto, il server esegue una semi-chiusura chiudendo solo il lato di scrittura della connessione di lettura/scrittura. Il server continua poi a leggere dalla connessione finché non riceve una chiusura corrispondente dal client, o finché non è ragionevolmente certo che il proprio stack TCP abbia ricevuto la conferma del client dei pacchetti contenenti l'ultima risposta del server. Infine, il server chiude completamente la connessione.
Non è noto se il problema del reset sia esclusivo del TCP o possa riscontrarsi anche in altri protocolli di connessione di trasporto.
Si noti che una connessione TCP semi-chiusa dal client non delimita un messaggio di richiesta, né implica che il client non sia più interessato a una risposta. In generale, non ci si può affidare ai segnali di trasporto per segnalare casi limite, poiché HTTP/1.1 è indipendente dal trasporto.
9.7. Avvio connessione TLS (TLS Connection Initiation)
Concettualmente, HTTP/TLS consiste semplicemente nell'inviare messaggi HTTP su una connessione protetta tramite TLS [TLS13].
Il client HTTP agisce anche come client TLS. Avvia una connessione al server sulla porta appropriata e invia il ClientHello TLS per iniziare l'handshake TLS. Quando l'handshake TLS è terminato, il client può quindi avviare la prima richiesta HTTP. Tutti i dati HTTP devono (MUST) essere inviati come "application data" TLS, ma per il resto sono trattati come una normale connessione per HTTP (incluso il potenziale riutilizzo come connessione persistente).
9.8. Chiusura connessione TLS (TLS Connection Closure)
TLS utilizza uno scambio di avvisi di chiusura prima della chiusura (non di errore) della connessione per fornire una chiusura sicura della connessione; vedere la sezione 6.1 di [TLS13]. Quando viene ricevuto un avviso di chiusura valido, un'implementazione può essere certa che nessun ulteriore dato verrà ricevuto su quella connessione.
Quando un'implementazione sa di aver inviato o ricevuto tutti i dati dei messaggi che le interessano, in genere rilevando i confini dei messaggi HTTP, può generare una "chiusura incompleta" inviando un avviso di chiusura e chiudendo poi la connessione senza attendere di ricevere l'avviso di chiusura corrispondente dal peer.
Una chiusura incompleta non mette in discussione la sicurezza dei dati già ricevuti, ma potrebbe indicare che i dati successivi sono stati troncati. Poiché TLS non è direttamente a conoscenza della struttura dei messaggi HTTP, è necessario esaminare i dati HTTP stessi per determinare se i messaggi sono completi. La gestione dei messaggi incompleti è definita nella sezione 8.
Quando incontra una chiusura incompleta, un client dovrebbe (SHOULD) considerare completate tutte le richieste per le quali ha ricevuto:
- tutti i dati specificati nel campo di intestazione Content-Length, oppure
- il chunk terminale di lunghezza zero (quando viene usato Transfer-Encoding con valore chunked).
Una risposta che non ha né codifica di trasferimento chunked né Content-Length è completa solo se è stato ricevuto un avviso di chiusura valido. Trattare un messaggio incompleto come completo potrebbe esporre le implementazioni ad attacchi.
Un client che rileva una chiusura incompleta dovrebbe (SHOULD) ripristinarsi in modo graduale.
I client devono (MUST) inviare un avviso di chiusura prima di chiudere la connessione. I client che non si aspettano di ricevere altri dati possono (MAY) scegliere di non attendere l'avviso di chiusura del server e chiudere semplicemente la connessione, generando così una chiusura incompleta sul lato server.
I server dovrebbero (SHOULD) essere preparati a ricevere una chiusura incompleta dal client, poiché il client è spesso in grado di individuare la fine dei dati del server.
I server devono (MUST) tentare di avviare uno scambio di avvisi di chiusura con il client prima di chiudere la connessione. I server possono (MAY) chiudere la connessione dopo aver inviato l'avviso di chiusura, generando così una chiusura incompleta sul lato client.