Aller au contenu principal

9. Gestion des connexions (Connection Management)

La messagerie HTTP est indépendante des protocoles de connexion de la couche transport ou session sous-jacents. HTTP ne présume qu'un transport fiable avec une livraison dans l'ordre des requêtes et la livraison correspondante dans l'ordre des réponses. La mise en correspondance des structures de requête et de réponse HTTP sur les unités de données d'un protocole de transport sous-jacent sort du cadre de cette spécification.

Comme décrit à la section 7.3 de [HTTP], les protocoles de connexion spécifiques à utiliser pour une interaction HTTP sont déterminés par la configuration du client et par l'URI cible. Par exemple, le schéma d'URI "http" (section 4.2.1 de [HTTP]) indique une connexion par défaut de TCP sur IP, avec un port TCP par défaut de 80, mais le client peut être configuré pour utiliser un proxy via une autre connexion, un autre port ou un autre protocole.

Les implémentations HTTP sont censées pratiquer la gestion des connexions, ce qui comprend la maintenance de l'état des connexions en cours, l'établissement d'une nouvelle connexion ou la réutilisation d'une connexion existante, le traitement des messages reçus sur une connexion, la détection des défaillances de connexion et la fermeture de chaque connexion. La plupart des clients maintiennent plusieurs connexions en parallèle, y compris plus d'une connexion par point de terminaison de serveur. La plupart des serveurs sont conçus pour maintenir des milliers de connexions simultanées, tout en contrôlant les files d'attente de requêtes afin de permettre un usage équitable et de détecter les attaques par déni de service.

9.1. Établissement (Establishment)​

Il est hors de la portée de cette spécification de décrire comment les connexions sont établies via divers protocoles de couche transport ou session. Chaque connexion HTTP correspond à une connexion de transport sous-jacente.

9.2. Association d'une réponse à une requête (Associating a Response to a Request)​

HTTP/1.1 n'inclut pas d'identifiant de requête permettant d'associer un message de requête donné à son ou ses messages de réponse correspondants. Par conséquent, il s'appuie sur le fait que l'ordre d'arrivée des réponses corresponde exactement à l'ordre dans lequel les requêtes sont émises sur la même connexion. Plus d'un message de réponse par requête ne se produit que lorsqu'une ou plusieurs réponses informatives (1xx ; voir la section 15.2 de [HTTP]) précèdent une réponse finale à la même requête.

Un client qui a plus d'une requête en attente sur une connexion doit (MUST) tenir une liste des requêtes en attente dans l'ordre d'envoi et doit (MUST) associer chaque message de réponse reçu sur cette connexion à la première requête en attente qui n'a pas encore reçu de réponse finale (non 1xx).

Si un client reçoit des données sur une connexion qui n'a pas de requêtes en attente, le client ne doit pas (MUST NOT) considérer ces données comme une réponse valide ; le client devrait (SHOULD) fermer la connexion, car la délimitation des messages est désormais ambiguë, sauf si les données ne consistent qu'en un ou plusieurs CRLF (qui peuvent être ignorés conformément à la section 2.2).

9.3. Persistance (Persistence)​

HTTP/1.1 utilise par défaut des "connexions persistantes", permettant à plusieurs requêtes et réponses d'être transportées sur une seule connexion. Les implémentations HTTP devraient (SHOULD) prendre en charge les connexions persistantes.

Un destinataire détermine si une connexion est persistante ou non en se fondant sur la version du protocole et sur le champ d'en-tête Connection (section 7.6.1 de [HTTP]) du message reçu le plus récemment, s'il existe :

  • Si l'option de connexion "close" est présente (section 9.6), la connexion ne persistera pas après la réponse en cours ; sinon,
  • Si le protocole reçu est HTTP/1.1 (ou ultérieur), la connexion persistera après la réponse en cours ; sinon,
  • Si le protocole reçu est HTTP/1.0, que l'option de connexion "keep-alive" est présente, que le destinataire n'est pas un proxy ou que le message est une réponse, et que le destinataire souhaite honorer le mécanisme "keep-alive" de HTTP/1.0, la connexion persistera après la réponse en cours ; sinon,
  • La connexion se fermera après la réponse en cours.

Un client qui ne prend pas en charge les connexions persistantes doit (MUST) envoyer l'option de connexion "close" dans chaque message de requête.

Un serveur qui ne prend pas en charge les connexions persistantes doit (MUST) envoyer l'option de connexion "close" dans chaque message de réponse qui n'a pas de code d'état 1xx (Informational).

Un client peut (MAY) envoyer des requêtes supplémentaires sur une connexion persistante jusqu'à ce qu'il envoie ou reçoive une option de connexion "close", ou reçoive une réponse HTTP/1.0 sans option de connexion "keep-alive".

Pour rester persistante, tous les messages d'une connexion doivent avoir une longueur de message autodéfinie (c'est-à-dire une longueur non définie par la fermeture de la connexion), comme décrit à la section 6. Un serveur doit (MUST) lire l'intégralité du corps du message de requête ou fermer la connexion après avoir envoyé sa réponse ; sinon, les données restantes sur une connexion persistante seraient interprétées à tort comme la requête suivante. De même, un client doit (MUST) lire l'intégralité du corps du message de réponse s'il a l'intention de réutiliser la même connexion pour une requête ultérieure.

Un serveur proxy ne doit pas (MUST NOT) maintenir une connexion persistante avec un client HTTP/1.0 (voir l'annexe C.2.2 pour des informations et une discussion sur les problèmes du champ d'en-tête Keep-Alive implémenté par de nombreux clients HTTP/1.0).

Voir l'annexe C.2.2 pour plus d'informations sur la compatibilité ascendante avec les clients HTTP/1.0.

9.3.1. Nouvelles tentatives de requêtes (Retrying Requests)​

Les connexions peuvent être fermées à tout moment, intentionnellement ou non. Les implémentations devraient anticiper la nécessité de se rétablir après des événements de fermeture asynchrones. Les conditions dans lesquelles un client peut réessayer automatiquement une séquence de requêtes en attente sont définies à la section 9.2.2 de [HTTP].

9.3.2. Mise en pipeline (Pipelining)​

Un client qui prend en charge les connexions persistantes peut (MAY) "mettre en pipeline" ses requêtes (c'est-à-dire envoyer plusieurs requêtes sans attendre chaque réponse). Un serveur peut (MAY) traiter en parallèle une séquence de requêtes mises en pipeline si elles utilisent toutes des méthodes sûres (section 9.2.1 de [HTTP]), mais il doit (MUST) envoyer les réponses correspondantes dans le même ordre que celui dans lequel les requêtes ont été reçues.

Un client qui met ses requêtes en pipeline devrait (SHOULD) réessayer les requêtes sans réponse si la connexion se ferme avant qu'il ait reçu toutes les réponses correspondantes. Lorsqu'il réessaie des requêtes mises en pipeline après une connexion défaillante (une connexion non fermée explicitement par le serveur dans sa dernière réponse complète), un client ne doit pas (MUST NOT) mettre en pipeline immédiatement après l'établissement de la connexion, car la première requête restante du pipeline précédent a pu provoquer une réponse d'erreur qui peut être à nouveau perdue si plusieurs requêtes sont envoyées sur une connexion fermée prématurément (voir le problème de réinitialisation TCP décrit à la section 9.6).

Les méthodes idempotentes (section 9.2.2 de [HTTP]) sont importantes pour la mise en pipeline, car elles peuvent être réessayées automatiquement après une défaillance de connexion. Un agent utilisateur ne devrait pas (SHOULD NOT) mettre en pipeline des requêtes après une méthode non idempotente, tant que le code d'état de la réponse finale de cette méthode n'a pas été reçu, à moins que l'agent utilisateur ne dispose d'un moyen de détecter les conditions de défaillance partielle impliquant la séquence mise en pipeline et de s'en rétablir.

Un intermédiaire qui reçoit des requêtes mises en pipeline peut (MAY) mettre ces requêtes en pipeline lorsqu'il les transfère vers l'intérieur, car il peut compter sur le ou les agents utilisateur sortants pour déterminer quelles requêtes peuvent être mises en pipeline en toute sécurité. Si la connexion entrante échoue avant de recevoir une réponse, l'intermédiaire de mise en pipeline peut (MAY) tenter de réessayer une séquence de requêtes qui n'ont pas encore reçu de réponse si les requêtes utilisent toutes des méthodes idempotentes ; sinon, l'intermédiaire de mise en pipeline devrait (SHOULD) transférer les réponses reçues, puis fermer la ou les connexions sortantes correspondantes afin que le ou les agents utilisateur sortants puissent se rétablir en conséquence.

9.4. Concurrence (Concurrency)​

Un client devrait limiter le nombre de connexions ouvertes simultanément qu'il maintient vers un serveur donné.

Les révisions précédentes de HTTP fixaient un nombre précis de connexions comme plafond, mais cela s'est révélé peu pratique pour de nombreuses applications. En conséquence, cette spécification n'impose pas de nombre maximal particulier de connexions mais encourage plutôt les clients à être prudents lorsqu'ils ouvrent plusieurs connexions.

Plusieurs connexions sont généralement utilisées pour éviter le problème de "blocage en tête de file" (head-of-line blocking), dans lequel une requête qui exige un traitement serveur important et/ou transfère un contenu très volumineux bloquerait les requêtes suivantes sur la même connexion. Toutefois, chaque connexion consomme des ressources du serveur.

De plus, l'utilisation de plusieurs connexions peut causer des effets indésirables dans les réseaux congestionnés. L'utilisation d'un nombre plus élevé de connexions peut également causer des effets indésirables dans des réseaux autrement non congestionnés, car leur comportement d'émission agrégé et initialement synchronisé peut provoquer une congestion qui n'aurait pas été présente si un nombre inférieur de connexions parallèles avait été utilisé.

Notez qu'un serveur peut rejeter le trafic qu'il juge abusif ou caractéristique d'une attaque par déni de service, comme un nombre excessif de connexions ouvertes provenant d'un seul client.

9.5. Défaillances et délais d'attente (Failures and Timeouts)​

Les serveurs ont généralement une valeur de délai d'attente au-delà de laquelle ils ne maintiennent plus une connexion inactive. Les serveurs proxy peuvent fixer une valeur plus élevée, car il est probable que le client établisse davantage de connexions via le même serveur proxy. L'utilisation de connexions persistantes n'impose aucune exigence sur la durée (ou l'existence) de ce délai d'attente, ni au client ni au serveur.

Un client ou un serveur qui souhaite déclencher un délai d'attente devrait (SHOULD) procéder à une fermeture progressive de la connexion. Les implémentations devraient (SHOULD) surveiller en permanence les connexions ouvertes à la recherche d'un signal de fermeture reçu et y répondre de manière appropriée, car la fermeture rapide des deux côtés d'une connexion permet de récupérer les ressources système allouées.

Un client, un serveur ou un proxy peut (MAY) fermer la connexion de transport à tout moment. Par exemple, un client peut avoir commencé à envoyer une nouvelle requête au moment même où le serveur a décidé de fermer la connexion "inactive". Du point de vue du serveur, la connexion est fermée alors qu'elle était inactive, mais du point de vue du client, une requête est en cours.

Un serveur devrait (SHOULD) maintenir les connexions persistantes lorsque c'est possible et laisser les mécanismes de contrôle de flux du transport sous-jacent résoudre les surcharges temporaires plutôt que de mettre fin aux connexions en s'attendant à ce que les clients réessaient. Cette dernière technique peut aggraver la congestion du réseau ou la charge du serveur.

Un client qui envoie un corps de message devrait (SHOULD) surveiller la connexion réseau à la recherche d'une réponse d'erreur pendant qu'il transmet la requête. Si le client voit une réponse indiquant que le serveur ne souhaite pas recevoir le corps du message et qu'il ferme la connexion, le client devrait (SHOULD) cesser immédiatement de transmettre le corps et fermer son côté de la connexion.

9.6. Fermeture (Tear-down)​

L'option de connexion "close" est définie comme un signal que l'expéditeur fermera cette connexion après l'achèvement de la réponse. Un expéditeur devrait (SHOULD) envoyer un champ d'en-tête Connection (section 7.6.1 de [HTTP]) contenant l'option de connexion "close" lorsqu'il a l'intention de fermer une connexion. Par exemple,

Connection: close

en tant que champ d'en-tête de requête, indique qu'il s'agit de la dernière requête que le client enverra sur cette connexion, tandis que dans une réponse, le même champ indique que le serveur va fermer cette connexion une fois le message de réponse terminé.

Notez que le nom de champ "Close" est réservé, car l'utilisation de ce nom comme champ d'en-tête pourrait entrer en conflit avec l'option de connexion "close".

Un client qui envoie une option de connexion "close" ne doit pas (MUST NOT) envoyer d'autres requêtes sur cette connexion (après celle contenant "close") et doit (MUST) fermer la connexion après avoir lu le message de réponse final correspondant à cette requête.

Un serveur qui reçoit une option de connexion "close" doit (MUST) déclencher la fermeture de la connexion (voir ci-dessous) après avoir envoyé la réponse finale à la requête qui contenait l'option de connexion "close". Le serveur devrait (SHOULD) envoyer une option de connexion "close" dans sa réponse finale sur cette connexion. Le serveur ne doit pas (MUST NOT) traiter d'autres requêtes reçues sur cette connexion.

Un serveur qui envoie une option de connexion "close" doit (MUST) déclencher la fermeture de la connexion (voir ci-dessous) après avoir envoyé la réponse contenant l'option de connexion "close". Le serveur ne doit pas (MUST NOT) traiter d'autres requêtes reçues sur cette connexion.

Un client qui reçoit une option de connexion "close" doit (MUST) cesser d'envoyer des requêtes sur cette connexion et fermer la connexion après avoir lu le message de réponse contenant l'option de connexion "close" ; si des requêtes supplémentaires mises en pipeline ont été envoyées sur la connexion, le client ne devrait pas (SHOULD NOT) supposer qu'elles seront traitées par le serveur.

Si un serveur effectue une fermeture immédiate d'une connexion TCP, il existe un risque important que le client ne puisse pas lire la dernière réponse HTTP. Si le serveur reçoit des données supplémentaires du client sur une connexion entièrement fermée, telles qu'une autre requête envoyée par le client avant de recevoir la réponse du serveur, la pile TCP du serveur enverra un paquet de réinitialisation au client ; malheureusement, le paquet de réinitialisation peut effacer les tampons d'entrée non acquittés du client avant qu'ils puissent être lus et interprétés par l'analyseur HTTP du client.

Pour éviter le problème de réinitialisation TCP, les serveurs ferment généralement une connexion par étapes. D'abord, le serveur effectue une demi-fermeture en ne fermant que le côté écriture de la connexion en lecture/écriture. Le serveur continue ensuite à lire depuis la connexion jusqu'à ce qu'il reçoive une fermeture correspondante de la part du client, ou jusqu'à ce que le serveur soit raisonnablement certain que sa propre pile TCP a reçu l'acquittement du client pour le ou les paquets contenant la dernière réponse du serveur. Enfin, le serveur ferme complètement la connexion.

On ne sait pas si le problème de réinitialisation est exclusif à TCP ou s'il peut également se rencontrer dans d'autres protocoles de connexion de transport.

Notez qu'une connexion TCP à demi-fermée par le client ne délimite pas un message de requête et n'implique pas non plus que le client ne s'intéresse plus à une réponse. En général, on ne peut pas se fier aux signaux de transport pour signaler des cas limites, car HTTP/1.1 est indépendant du transport.

9.7. Initiation de connexion TLS (TLS Connection Initiation)​

Conceptuellement, HTTP/TLS consiste simplement à envoyer des messages HTTP sur une connexion sécurisée via TLS [TLS13].

Le client HTTP agit également comme client TLS. Il initie une connexion au serveur sur le port approprié et envoie le ClientHello TLS pour commencer la poignée de main TLS. Lorsque la poignée de main TLS est terminée, le client peut alors initier la première requête HTTP. Toutes les données HTTP doivent (MUST) être envoyées en tant que "application data" TLS, mais sont par ailleurs traitées comme une connexion normale pour HTTP (y compris une réutilisation potentielle en tant que connexion persistante).

9.8. Fermeture de connexion TLS (TLS Connection Closure)​

TLS utilise un échange d'alertes de fermeture avant la fermeture de connexion (non-erreur) pour fournir une fermeture de connexion sécurisée ; voir section 6.1 de [TLS13]. Lorsqu'une alerte de fermeture valide est reçue, une implémentation peut être assurée qu'aucune donnée supplémentaire ne sera reçue sur cette connexion.

Lorsqu'une implémentation sait qu'elle a envoyé ou reçu toutes les données de message qui l'intéressent, généralement en détectant les limites des messages HTTP, elle peut générer une "fermeture incomplète" en envoyant une alerte de fermeture puis en fermant la connexion sans attendre de recevoir l'alerte de fermeture correspondante de son pair.

Une fermeture incomplète ne remet pas en cause la sécurité des données déjà reçues, mais elle pourrait indiquer que les données suivantes ont été tronquées. Comme TLS n'a pas directement connaissance du cadrage des messages HTTP, il est nécessaire d'examiner les données HTTP elles-mêmes pour déterminer si les messages sont complets. Le traitement des messages incomplets est défini à la section 8.

Lorsqu'il rencontre une fermeture incomplète, un client devrait (SHOULD) considérer comme terminées toutes les requêtes pour lesquelles il a reçu soit

  1. autant de données que spécifié dans le champ d'en-tête Content-Length, soit
  2. le chunk terminal de longueur nulle (lorsqu'un Transfer-Encoding de valeur chunked est utilisé).

Une réponse qui n'a ni codage de transfert chunked ni Content-Length n'est complète que si une alerte de fermeture valide a été reçue. Traiter un message incomplet comme complet pourrait exposer les implémentations à des attaques.

Un client qui détecte une fermeture incomplète devrait (SHOULD) se rétablir gracieusement.

Les clients doivent (MUST) envoyer une alerte de fermeture avant de fermer la connexion. Les clients qui ne s'attendent pas à recevoir davantage de données peuvent (MAY) choisir de ne pas attendre l'alerte de fermeture du serveur et de simplement fermer la connexion, générant ainsi une fermeture incomplète du côté du serveur.

Les serveurs devraient (SHOULD) être préparés à recevoir une fermeture incomplète de la part du client, car le client peut souvent localiser la fin des données du serveur.

Les serveurs doivent (MUST) tenter d'initier un échange d'alertes de fermeture avec le client avant de fermer la connexion. Les serveurs peuvent (MAY) fermer la connexion après avoir envoyé l'alerte de fermeture, générant ainsi une fermeture incomplète du côté du client.