Cookies, sessions et persistance
Introduction
HTTP (HyperText Transfer Protocol) a été conçu pour prendre en charge un modèle sans état de transfert de données d’un serveur vers un client, fondé sur un mécanisme de requête-réponse. Sa première version, la version 1.0, ne permettait qu’un rapport de 1:1 entre les requêtes et les connexions (c’est-à-dire qu’une seule paire requête-réponse était prise en charge par connexion).
La version 1.1 a porté ce ratio à N:1, c’est-à-dire plusieurs requêtes par connexion. Cette évolution visait à répondre à la complexité croissante des pages web, notamment au grand nombre d’objets et d’éléments devant être transférés du serveur au client.
Avec l’adoption de la version 2.0, HTTP a continué à prendre en charge un modèle permettant plusieurs requêtes par connexion. Ses changements les plus radicaux concernent l’échange des en-têtes et le passage d’un transfert textuel à un transfert binaire.
À un moment donné, HTTP est devenu bien plus qu’un simple mécanisme permettant de transférer du texte et des images d’un serveur vers un client : il s’est transformé en plateforme applicative. L’omniprésence du navigateur, sa nature multiplateforme et la facilité avec laquelle les applications pouvaient être déployées sans avoir à supporter le coût élevé de la prise en charge de plusieurs systèmes d’exploitation et environnements étaient assurément séduisantes. Malheureusement, HTTP n’a pas été conçu comme un protocole de transport pour les applications, mais pour transférer des documents. Même les usages modernes de HTTP, notamment les API, reposent sur une charge utile assimilable à un document. JSON en est un bon exemple : il s’agit d’un format de données composé de paires clé-valeur et transmis sous forme de texte. Bien que les documents et les protocoles applicatifs soient généralement basés sur du texte, leur ressemblance s’arrête là.
Les applications traditionnelles ont besoin d’un moyen de conserver leur état, contrairement aux documents. Elles reposent sur des flux logiques et des processus qui nécessitent de savoir où en est l’utilisateur à un instant donné, ce qui implique de gérer un état. Malgré sa nature intrinsèquement sans état, HTTP est devenu de facto le protocole de transport des applications web. Grâce à ce qui constitue certainement l’une des astuces les plus largement adoptées et les plus utiles de l’histoire de l’informatique, HTTP a été doté d’un mécanisme permettant de suivre l’état tout au long de l’utilisation d’une application. C’est là qu’interviennent les sessions et les cookies.
Sessions
Transformer le sans état en avec état
Les sessions permettent aux serveurs web et applicatifs de conserver un état. Ces simples blocs de mémoire sont associés à chaque connexion TCP établie avec un serveur web ou applicatif et servent à stocker en mémoire les informations des applications basées sur HTTP.
Lorsqu’un utilisateur se connecte pour la première fois à un serveur, une session est créée et associée à cette connexion. Les développeurs utilisent ensuite cette session pour stocker des données utiles à l’application. Il peut s’agir d’informations importantes, telles qu’un identifiant client, ou de données moins essentielles, comme vos préférences d’affichage de la page d’accueil du site.
Les paniers d’achat sont le meilleur exemple de l’utilité des sessions, car nous avons presque tous déjà effectué des achats en ligne. Les articles d’un panier sont conservés pendant toute la durée d’une « session », car chacun d’eux est représenté d’une manière ou d’une autre dans la session sur le serveur. Les applications de configuration ou de personnalisation de produits sous forme d’assistant en sont un autre bon exemple. Ces « mini-applications » vous permettent de parcourir un ensemble d’options et de les sélectionner ; à la fin, vous êtes généralement surpris par le coût estimé de tous les accessoires et fonctionnalités que vous avez ajoutés. À mesure que vous passez d’un « écran d’options » à l’autre, les options déjà choisies sont stockées dans la session afin de pouvoir être facilement récupérées, ajoutées ou supprimées.
Les applications modernes sont conçues pour être sans état, mais leur architecture ne respecte pas toujours ce principe. Les méthodes modernes de mise à l’échelle reposent souvent sur des modèles architecturaux tels que le partitionnement, qui nécessite d’acheminer les requêtes en fonction de données pouvant être indexées, comme un nom d’utilisateur ou un numéro de compte. Cela impose une forme d’approche avec état, puisque les données indexables sont transmises avec chaque requête afin de garantir un acheminement correct et le bon fonctionnement de l’application. À cet égard, les applications et API modernes « sans état » nécessitent souvent une attention et un entretien comparables à ceux de leurs prédécesseurs avec état.
Le problème est que les sessions sont liées aux connexions et que les connexions laissées inactives trop longtemps finissent par expirer. En outre, la définition de « trop longtemps » diffère sensiblement selon qu’elle s’applique aux connexions ou aux sessions. Par exemple, la configuration par défaut de certains serveurs web prévoit de fermer une connexion après 15 secondes d’inactivité, c’est-à-dire lorsqu’aucune nouvelle requête n’a été effectuée. À l’inverse, sur ces serveurs web, une session reste par défaut en mémoire pendant 300 secondes, soit 5 minutes. Ces deux paramètres sont manifestement incompatibles : une fois la connexion expirée, à quoi sert la session si elle est associée à cette connexion ?
Vous pourriez penser qu’il suffit d’augmenter le délai d’expiration de la connexion pour l’aligner sur celui de la session et ainsi remédier à cette disparité. Toutefois, cela signifie que vous risquez de mobiliser de la mémoire pour maintenir une connexion qui sera peut-être utilisée ou non. Cette mesure peut réduire le nombre total d’utilisateurs simultanés pris en charge par votre serveur et, à terme, nuire à ses performances. Et il n’est évidemment pas souhaitable de réduire le délai d’expiration de la session pour l’aligner sur celui de la connexion, car la plupart des gens mettent plus de cinq minutes à faire leurs achats ou à personnaliser leur nouveau jouet.
Remarque importante : Bien que HTTP/2 résolve certains de ces problèmes, il en introduit d’autres liés au maintien de l’état. Même si la spécification ne l’impose pas, les principaux navigateurs n’autorisent HTTP/2 que sur TLS/SSL. Les deux protocoles nécessitent une connexion persistante afin d’éviter le coût en performances d’une renégociation, ce qui exige à son tour une gestion des sessions, autrement dit un comportement avec état.
Ainsi, vous vous retrouvez avec des sessions qui restent en mémoire sur le serveur même après que les connexions associées ont été interrompues pour cause d’inactivité, consommant de précieuses ressources et risquant d’irriter les utilisateurs pour lesquels votre application ne fonctionne tout simplement pas.
Heureusement, ce problème peut être résolu grâce à l’utilisation de cookies.
Cookies
La piste des miettes ramène à la maison
Les cookies sont de petits éléments de données stockés par le navigateur sur l’appareil de l’utilisateur. Ils peuvent contenir, et contiennent effectivement, toutes sortes d’informations intéressantes sur vous, vos applications et les sites que vous consultez. Le terme « cookie » vient de « magic cookie », un concept bien connu dans l’univers informatique UNIX, qui en a inspiré à la fois l’idée et le nom. Les cookies sont créés et échangés entre le navigateur et le serveur par l’intermédiaire de l’en-tête HTTP Cookie.
Cookie: JSESSIONID=9597856473431 Cache-Control: no-cache Host: 127.0.0.2:8080 Connection: Keep-Alive
Le navigateur sait automatiquement qu’il doit enregistrer le cookie figurant dans l’en-tête HTTP dans un fichier sur votre ordinateur, et il assure le suivi des cookies pour chaque domaine. Les cookies associés à un domaine donné sont toujours transmis au serveur par le navigateur dans les en-têtes HTTP. Les développeurs d’applications web peuvent donc récupérer ces valeurs en les demandant simplement côté serveur.
Le problème de la durée des sessions et des connexions est résolu grâce à un cookie. Presque toutes les applications web modernes génèrent un « identifiant de session » et le transmettent sous forme de cookie. Cela permet à l’application de retrouver la session sur le serveur, même après la fermeture de la connexion ayant servi à la créer. Grâce à cet échange d’identifiants de session, l’état est conservé, même avec un protocole sans état comme HTTP. Mais que se passe-t-il lorsque l’utilisation d’une application web dépasse les capacités d’un seul serveur web ou serveur d’applications ? En général, un équilibreur de charge ou, dans les architectures actuelles, un contrôleur de mise à disposition d’applications (ADC) est mis en place afin de faire évoluer l’application et de garantir à tous les utilisateurs une disponibilité et des performances satisfaisantes.
Dans les applications modernes, les cookies peuvent encore être utilisés, mais d’autres en-têtes HTTP deviennent essentiels. Les clés d’API servant à l’authentification et à l’autorisation sont souvent transmises via un en-tête HTTP, tout comme d’autres en-têtes personnalisés qui véhiculent les données nécessaires au routage et à la mise à l’échelle adéquate des services back-end. Qu’un « cookie » traditionnel ou un autre en-tête HTTP soit utilisé pour transporter ces données importe moins que de reconnaître leur importance pour l’architecture globale.
Le problème est que les algorithmes d’équilibrage de charge visent généralement uniquement à répartir les requêtes entre les serveurs. Les techniques d’équilibrage de charge reposent sur des algorithmes standard du secteur, tels que la répartition à tour de rôle (round robin), le plus petit nombre de connexions ou le temps de réponse le plus rapide. Aucun d’entre eux ne tient compte de l’état, et chaque requête d’un même utilisateur adressée à une application peut être distribuée à un serveur différent. Tout le travail effectué pour gérer un état avec HTTP devient alors inutile, car les données stockées dans la session d’un serveur sont rarement partagées avec les autres serveurs du « pool ».
C’est là que le concept de persistance prend tout son sens.
Persistance
Le lien qui unit
La persistance, également appelée affinité de session, est une technique mise en œuvre par les ADC pour garantir que les requêtes d’un même utilisateur sont toujours dirigées vers le serveur sur lequel sa session a débuté. Certains produits et services d’équilibrage de charge désignent cette technique par le terme « sessions persistantes », une appellation tout à fait appropriée.
La persistance est utilisée depuis longtemps pour l’équilibrage de charge des sites utilisant SSL/TLS, car une fois le processus de négociation — particulièrement gourmand en ressources de calcul — terminé et les clés échangées, le recommencer entraînerait une dégradation importante des performances. Les ADC ont donc mis en œuvre la persistance des sessions SSL afin que les utilisateurs soient toujours dirigés vers le serveur auquel ils se sont initialement connectés.
Au fil des années, les implémentations des navigateurs ont rendu nécessaire le développement d’une technique permettant d’éviter la renégociation coûteuse de ces sessions. Cette technique est appelée persistance basée sur les cookies.
Plutôt que de s’appuyer sur l’identifiant de session SSL/TLS, l’équilibreur de charge insérait un cookie afin d’identifier de manière unique la session lors du premier accès d’un client au site, puis se référait à ce cookie dans les requêtes suivantes pour maintenir la connexion avec le serveur approprié.
Le concept de persistance basée sur les cookies a depuis été appliqué aux sessions applicatives. Il utilise les informations d’identifiant de session générées par les serveurs web et applicatifs afin de garantir que les requêtes d’un utilisateur sont toujours dirigées vers le même serveur au cours d’une même session. Sans cette fonctionnalité, les applications nécessitant un équilibrage de charge devraient trouver un autre moyen de partager les informations de session ou augmenter les délais d’expiration des sessions et des connexions, au point que le nombre de serveurs nécessaires pour prendre en charge leur base d’utilisateurs deviendrait rapidement ingérable.
Bien que la forme de persistance la plus courante repose sur l’utilisation d’identifiants de session transmis dans l’en-tête HTTP, les contrôleurs de mise à disposition d’applications (ADC) peuvent aujourd’hui assurer la persistance à partir d’autres types de données. Toute donnée pouvant être stockée dans un cookie ou extraite des en-têtes IP, TCP ou HTTP peut servir à maintenir la persistance d’une session. En fait, toute donnée contenue dans un message applicatif permettant d’identifier l’utilisateur de manière unique peut être utilisée par un ADC intelligent pour maintenir une connexion persistante entre le navigateur et un serveur.
Conclusion
HTTP est peut-être un protocole sans état, mais nous avons réussi à y intégrer de force la gestion d’un état. Grâce à la persistance et aux contrôleurs de mise à disposition d’applications, il est possible de concevoir des applications web hautement disponibles et performantes sans compromettre l’intégration quelque peu fragile des cookies et des sessions nécessaire au maintien de l’état.
Ce sont ces fonctionnalités qui confèrent un état à HTTP, même si sa mise en œuvre et son exécution restent sans état. Sans les cookies, les sessions et la persistance, nous aurions certainement trouvé un protocole avec état sur lequel bâtir nos applications. Au lieu de cela, les fonctionnalités offertes par les contrôleurs de mise à disposition d’applications assurent l’intermédiation entre les navigateurs (clients) et les serveurs afin de fournir cette capacité, étendant ainsi l’utilité de HTTP au-delà des pages web statiques et des applications traditionnelles, jusqu’aux architectures modernes fondées sur les microservices et à la coqueluche de l’économie numérique : l’API.
PUBLIÉ LE 19 JANVIER 2018
