Cours - Lirmm
Transcription
Cours - Lirmm
IUT Montpellier - Architecture (DU) V. Poupet Cours no 5 : Hypertext Transfer Protocol (HTTP) 1 Le protocole HTTP Lorsqu’un utilisateur (client) veut obtenir une page web, il s’adresse à un serveur web. Il effectue alors une requête HTTP, qui est suivie d’une réponse HTTP de la part du serveur. 1.1 Requête HTTP La requête HTTP la plus simple est de la forme : GET lapin.html HTTP/1.0 Le premier mot (en majuscule) indique la nature de la requête. Ici c’est une requête GET (la plus courante) c’est-à-dire que le client veut obtenir une ressource. Elle est suivie d’une chaîne permettant d’identifier la ressources (ici on veut le fichier lapin.html) et de la version du protocole utilisé (HTTP v.1.0). 1.1.1 Structure d’une requête HTTP Une requête HTTP est constituée d’un en-tête, suivi d’une ligne vide et du corps de la requête. L’en-tête contient une première ligne contenant la méthode employée, la requête proprement dite et la version du protocole utilisée. Les lignes suivantes (qui ne peuvent pas être vides) contiennent différentes informations sous la forme nom_du_champ: valeur_du_champ Le contenu de la requête (après la ligne vide) dépend de la méthode utilisée. Ainsi dans le cas d’une requête GET il est vide, tandis que la méthode POST utilise le corps de la requête pour transmettre les données. GET /images/lapin.jpg HTTP/1.1 Accept: image/jpeg, text/html, */* Accept-Language: fr Accept-Encoding: gzip, deflate User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/418.9.1 (KHTML, like Gecko) Safari/419.3 Connection: keep-alive Authorization: Basic bGFwaW46dG9ydHVl Host: localhost:7777 On reconnaît la première ligne (ici le protocole est HTTP v.1.1 et le fichier demandé se trouve dans un répertoire « images »). Les trois lignes suivantes indiquent le type de document que le client attend de la part du serveur. Ici il est indiqué que le client voudrait une image au format jpeg, ou un fichier html ou bien à défaut n’importe quel type de fichier. Par ailleurs il préfère les documents en langue française (fr) et accepte que le fichier soit compressé à l’aide de l’un des algorithmes gzip ou deflate. Le champ User-Agent donne des informations sur le client (navigateur utilisé, système d’exploitation, etc.). Pour des raisons de place sur la page, la ligne a ici été coupée mais elle ne doit pas l’être dans la requête HTTP. Le champ Connection indique que la socket utilisée pour la connexion doit être maintenue ouverte après la réponse du serveur. Dans les premières versions du protocole HTTP le serveur fermait la 1 connexion avec le client dès qu’il lui avait envoyé le fichier demandé. Il fallait alors créer une nouvelle connexion pour le fichier suivant. Aujourd’hui, la plupart des pages sont constituées de nombreux fichiers (un .html et plusieurs images par exemple). Lorsque le client demande la page, il ne recevra que le .html. En essayant de l’afficher il découvrira alors qu’il lui faut également les autres fichiers et enverra une demande pour chacun. La demande d’une nouvelle connexion pour chaque élément de la page ralentit beaucoup la communication et il a été décidé d’ajouter la possibilité de maintenir la connexion ouverte entre le client et le serveur jusqu’à ce que l’un des deux partis décide explicitement de la fermer (le client lorsqu’il a obtenu tous les fichiers nécessaires pour afficher la page ou le serveur en cas d’inactivité trop longue du client). Ce comportement keep-alive est maintenant le comportement par défaut dans le protocole HTTP v.1.1. Le champ Authorization permet de s’identifier auprès du serveur. Dans l’exemple ci-dessus, on utilise la méthode Basic qui consiste simplement en l’envoi d’un identifiant et d’un mot de passe. Ces deux données sont encodées en base 64 afin de s’assurer que les symboles spéciaux pouvant apparaître ne poseront pas de problème. Dans l’exemple, la chaîne de caractères bGFwaW46dG9ydHVl est l’encodage en base 64 de la chaîne lapin:tortue. Notons que l’information n’est pas cryptée puisque tout le monde peut décoder cette chaîne (en utilisant un algorithme standard) et que cela représente donc un risque de sécurité (quiconque intercepte la requête HTTP peut connaître l’identifiant et le mot de passe). Le champ Authorization n’est en général pas nécessaire. Il n’est transmis que lorsque l’utilisateur doit accéder à des informations sensibles. En général on crypte alors la connexion avec le serveur HTTP en utilisant le protocole SSL afin que le mot de passe ne puisse pas être intercepté. Enfin, dans notre exemple, le champ Host indique le nom du serveur web que le client veut contacter. Ce champ, qui était optionnel en HTTP 1.0 mais est devenu obligatoire en HTTP 1.1, peut sembler inutile puisque le serveur est censé connaître son propre nom. Cependant il est particulièrement utile lorsque plusieurs noms de serveurs sont associés à la même adresse IP. On peut en effet avoir deux (ou plus) serveurs serveur1.com et serveur2.com fonctionnant sur la même machine. Lorsque le client veut obtenir un fichier sur le serveur serveur1.com, il interroge le DNS qui transforme ce nom de serveur en adresse IP. Lorsque la requête HTTP arrive à la machine correspondant à l’IP demandée cette dernière ne peut pas savoir a priori à quel serveur la demande s’adresse. L’utilisation du champ Host dans l’en-tête de la requête permet de résoudre ce problème. Remarque : Il existe encore bien d’autres champs qui peuvent être ajoutés à une requête HTTP, toutefois dans le cas d’une requête de type GET aucun n’est obligatoire en HTTP 1.0 et seul le champ Host l’est en HTTP 1.1. 1.1.2 Quelques types de requêtes Il existe huit méthodes (ou verbes) de requêtes HTTP : GET. C’est la méthode la plus couramment employée. Le client demande au serveur de lui envoyer une ressource. HEAD. Semblable à GET dans sa syntaxe, mais ici le client ne demande que les en-têtes de la réponse du serveur (informations sur la ressource qui aurait été renvoyée, mais pas la ressource elle-même). Cette méthode peut être utilisée pour vérifier qu’une ressource est disponibles, pour savoir si la version du serveur est plus récente que celle du client, pour connaître la taille de la ressource, etc. sans pour autant transmettre la ressource. POST. Cette méthode est proche de la méthode GET mais ici le client soumet des données (la plupart du temps entrées à l’aide d’un formulaire HTML) dans le corps de la requête. PUT. Permet au client d’envoyer une ressource au serveur (modifier un fichier existant par exemple). Cette méthode est aujourd’hui très peu utilisée car elle présente des risques de sécurité évidents. On préfère utiliser le protocole FTP pour ce genre de manipulations. DELETE. Le client demande la suppression d’une ressource. Tout comme la méthode PUT elle n’est plus utilisée aujourd’hui pour raisons de sécurité. TRACE. Demande au serveur de renvoyer la requete qu’il reçoit. Cette méthode permet au client de déterminer les modifications éventuelles appliquées à sa requête par les serveurs intermédiaires par lesquelles elle est passée. 2 OPTIONS. Le client demande au serveur de lui envoyer la liste des méthodes supportées. CONNECT. Demande la conversion de la connexion HTTP en un tunnel TCP/IP. Cette méthode est principalement utilisée pour établir des communications cryptées à l’aide du protocole SSL (HTTPS) lorsque l’on utilise un proxy non crypté. 1.2 Réponse HTTP La réponse du serveur HTTP à une requête est également constituée d’un en-tête suivi d’une ligne vide et du corps de la réponse. À la très simple requête GET lapin.html HTTP/1.0 pourrait correspondre la réponse (également minimaliste) : HTTP/1.0 200 OK Content-Type: text/html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <HTML> <HEAD> <TITLE>Lapin</TITLE> </HEAD> <BODY> Ce matin un lapin a tué un chasseur... </BODY> </HTML> On remarque tout d’abord que le corps correspond exactement au corps du fichier demandé (ici un fichier html mais ce pourrait également être le contenu d’une image ou tout autre type de fichier). C’est ce qu’avait demandé le client. L’en-tête quant à lui est constitué de méta-données. La première ligne donne le protocole utilisé (ici HTTP 1.0), suivi d’un code de retour (trois chiffres et une description). Cette première ligne peut ensuite être suivie de nombreux champs d’informations. Remarque : Tout comme dans le cas d’une requête HTTP, la première ligne vide indique au client la fin de l’en-tête et le début du corps de la réponse. 1.2.1 Les codes de retour Les codes de retour sont toujours constitués de 3 chiffres. Ils sont divisés en 5 catégories : 1xx. Informations. Par exemple 100 CONTINUE signifie que le serveur a bien reçu l’en-tête de la requête et qu’il attend le corps (dans le cas d’une requete de type POST par exemple). 2xx. Succès. 200 OK, 202 ACCEPTED, etc. 3xx. Redirection. La ressource n’est pas accessible directement, et le client doit donc effectuer d’autres tâches avant que la requête ne puisse être complétée. Par exemple 300 MULTIPLE CHOICES demande à l’utilisateur de préciser sa demande car l’URI correspond à plusieurs ressources différentes, 301 MOVED PERMANENTLY signifie que l’utilisateur doit réitérer sa demande vers une autre URI, 303 SEE OTHER indique à l’utilisateur de chercher la ressource à une autre URI, etc. 4xx Erreur du client. La requête est mal formulée ou ne peut être exécutée. Les exemples les plus fréquents sont 400 BAD REQUEST (la requête est mal formulée), 403 FORBIDDEN (le client n’a pas accès à la ressource) ou encore 404 NOT FOUND (le fichier n’existe pas). 5xx Erreur du serveur sur une requête apparemment valide : 500 INTERNAL SERVER ERROR, 501 NOT IMPLEMENTED (le serveur ne sait pas traiter ce type de requêtes), etc. 3 1.2.2 Exemple d’en-tête La plupart du temps le serveur donne de nombreuses informations dans l’en-tête de la réponse HTTP. On pourra par exemple avoir l’en-tête suivant d’un réponse à une requête de type GET : HTTP/1.1 200 OK Date: Thu, 08 Feb 2007 20:48:45 GMT Server: Apache Last-Modified: Thu, 08 Feb 2007 19:20:26 GMT Content-Language: fr Content-MD5: d72f9499b1b0a9c5c611186e0f98376e Content-Length: 68975 Content-Type: text/html; charset=iso-8859-1 On trouve donc dans cet en-tête la date (et l’heure) à laquelle la réponse à été envoyée, le type du serveur web, la date de dernière modification du fichier demandé, la langue du document, le code MD5 du fichier, la taille du fichier en octets ainsi que le type MIME du document (ici un fichier de type text, dont le sous-type est html) et le jeu de caractères dans lequel il est encodé. 2 Les serveurs web Lorsqu’un utilisateur veut obtenir un document sur Internet (la plupart du temps par l’intermédiaire de son navigateur) il interroge un serveur web qui est chargé de trouver l’information et de la lui transmettre. Nous avons vu précédemment comment le navigateur contactait le serveur à l’aide de l’adresse (que ce soit un nom de domaine ou une adresse IP) et comment la connexion s’établissait à l’aide de sockets TCP/IP. Nous allons ici détailler les étapes effectuées par le serveur entre la réception de la requête HTTP et la transmission de la réponse. Nous prendrons ici comme exemple le serveur Apache HTTP server 1 qui est un serveur libre couramment utilisé (plus de la moitié des sites webs aujourd’hui sont servis par Apache), les autres serveurs ayant un comportement très similaire. 2.1 Un exemple de requête Considérons la requête suivante : POST /dossier/fichier.html HTTP/1.1 Accept: text/html, */* Accept-Language: fr,en Accept-charset: iso-8859-1, utf-8 Accept-Encoding: gzip, deflate User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) Authorization: Basic bGFwaW46dG9ydHVl Connection: keep-alive Host: www.serveur.org:80 Cette requête, envoyée au serveur www.serveur.org (un autre serveur web refusera la requête car le champ Host serait alors incorrect) précise que l’utilisateur veut recevoir le fichier /dossier/fichier.html (la racine de l’arborescence est fixée par le serveur web, ce n’est en général pas la racine du système de fichiers sur lequel se trouve le serveur). Tous les autres champs ont déjà été présentés précédemment. Lorsqu’il reçoit une requête HTTP, le serveur Apache exécute alors successivement les étapes suivantes. 1. http://www.apachefrance.com/ 4 2.1.1 Child Initialization Un serveur Apache peut être constitué de plusieurs processus (comme c’est souvent le cas sur un système Unix) ou d’un unique processus contenant plusieurs threads (comme c’est le cas sous Windows). Quelle que soit son architecture, lorsqu’il reçoit une requête, celle-ci est traitée en parallèle (par un processus fils ou un nouveau thread) généré à la réception de la requête. 2.1.2 Post Read Request et Header Parser Cette étape correspond à la lecture de l’en-tête de la requête. Les champs sont parcourus et le serveur décide alors s’il doit ou non poursuivre le traitement. Si la requête est mal formulée elle peut être rejetée à cette étape. S’il manque un champ indispensable (comme le champ Host en HTTP 1.1) la demande est également rejetée. L’étape Header Parser découpe également les informations contenues dans l’en-tête pour séparer tous les champs importants (l’URL demandée, les types de retour attendus, etc.) qui seront ensuite transmis aux étapes suivantes. 2.1.3 Translate Name L’URL de la ressource demandée est ici transformée en un nom de fichier. Dans notre exemple, si la racine du serveur HTTP se trouve dans le répertoire /home/lapin/web/server/ (dans l’arborescence de la machine sur laquelle se trouve le serveur) alors le serveur traduit l’URL /dossier/fichier.html en /home/lapin/web/server/dossier/fichier.html. Il est également possible de faire faire au serveur des transformations plus complexes. Il est par exemple possible de demander à ce que toutes les URL du type /script/nom soient transformées en /usr/bin/nom afin que le serveur puisse exécuter des instructions sur le système de fichier en recevant des requêtes HTTP. 2.1.4 Check Access Une fois que le serveur connaît le nom du fichier cible, il vérifie que ce fichier est accessible. Cette vérification précède toute forme d’authentification de l’utilisateur, c’est-à-dire que le serveur vérifie simplement que le fichier existe et qu’il pourrait l’obtenir s’il le fallait. 2.1.5 Check User ID Le serveur doit ici déterminer l’identifiant de l’utilisateur et vérifier que les informations fournies suffisent à confirmer cette identité. Dans notre exemple, le serveur voit que l’authentification se fait selon le protocole Basic, il décode donc la chaîne de caractères bGFwaW46dG9ydHVl, obtient l’identifiant et le mot de passe fournis par l’utilisateur (lapin et tortue respectivement) et va demander au système si l’utilisateur lapin existe et si son mot de passe est bien tortue. Si cette vérification échoue, le serveur renvoie l’erreur HTTP_UNAUTHORIZED et l’en-tête de la réponse contient les détails indiquant à l’utilisateur quelles informations il doit fournir pour s’identifier correctement. 2.1.6 Check Auth La procédure précédente se contente de vérifier l’identifiant annoncé par l’utilisateur (est-il bien celui qu’il prétend être) mais ne détermine pas pour autant s’il a accès à l’information demandée. C’est l’étape d’autorisation qui s’en charge. Connaissant l’identité de l’utilisateur, le serveur peut maintenant vérifier que cet utilisateur a bien accès à la ressource demandée (la plupart du temps cela se fait en interrogeant le système de fichiers). Si l’accès n’est pas autorisé, c’est encore une fois l’erreur HTTP_UNAUTHORIZED qui est levée et revoyée à l’utilisateur en lui demandant une nouvelle authentification (sous une autre identité). 5 2.1.7 Type Checker À ce stade-ci, la requête est presque entièrement traitée. La ressource a été localisée et toutes les vérifications ont été faites pour s’assurer qu’elle pouvait être transmise au client. Il ne reste alors plus qu’à déterminer quel handler (module qui manipule le fichier) va effectivement traiter la demande. En effet, s’il est vrai que la tâche est quasi-instantanée dans le cas d’une requête d’un fichier HTTP simple (il suffit de joindre le fichier tel quel à la réponse) il est parfois nécessaire de faire appel à des programmes spécifiques dans des cas plus complexes. Ainsi, si la requête correspond à un script il faut appeler l’interpréteur correspondant (PHP, Python, Perl, etc.), ou si le fichier demandé doit être modifié avant d’être envoyé (certaines pages sont traduites avant d’être transmises, ou bien il faut parfois insérer les champs envoyés pas l’utilisateur dans la page, etc.). Afin de déterminer le handler approrié le serveur détermine le type MIME de la ressource demandée à partir de son URL (par exemple text/html, ou image/jpeg) et produit une chaîne de caractères à transmettre au handler. Ce module peut également générer une chaîne de caractères à fournir au handler en fonction des informations connues jusqu’ici (par exemple la ligne de commande d’appel d’un script). 2.1.8 Prerun Fixups Dernière étape avant de laisser travailler les handlers. C’est ici que sont effectuées les dernières tâches éventuellement nécessaires au bon fonctionnement du handler, comme par exemple la définition des variables d’environnement pour un script. La plupart des modules n’effectuent aucune action à ce stade-ci car tout ce qui devait être fait a été traité lors de l’une des étapes précédentes. 2.1.9 Handlers La requête a été entièrement préparée et peut maintenant être effectivement traitée par le programme approprié. C’est à ce stade-là que les interpréteurs de scripts sont appelés (PHP, Perl, Java, etc.), que l’on peut traduire le document demandé dans une langue spécifiée par le client, etc. La plupart des handlers renvoie directement la réponse HTTP (il est alors nécessaire de s’assurer que le handler ajoute bien l’en-tête de la réponse HTTP), mais ils peuvent aussi parfois se contenter de transmettre en interne le contenu de la réponse au serveur. Ce dernier devra alors produire l’en-tête. 2.1.10 Logger Une fois que la requête a été entièrement traitée et que la réponse a été renvoyée au client, le serveur inscrit dans un fichier de log les détails de la transaction afin d’en garder une trace (par exemple la totalité de la requête HTTP, les détails de connexion avec l’ordinateur distant et l’en-tête de la réponse transmise). 2.1.11 Child Exit Le processus ou thread qui avait été initialisé pour traiter la requête HTTP peut maintenant être terminé. 3 Sessions HTTP Le protocole HTTP est un protocole sans état (state-less) c’est-à-dire qu’aucune information n’est conservée entre deux requêtes successives. Il ne prévoit donc aucune méthode pour spécifier explicitement qu’une requête est la suite d’une autre. Il est pourtant parfois confortable de pouvoir s’identifier auprès d’un site web et de faire en sorte que par la suite le serveur reconnaisse l’utilisateur et puisse adapter la réponse à cet utilisateur. Pour cela, il est nécessaire de mettre en place un susyème de sessions. Une session HTTP est une suite de requêtes HTTP (d’un client vers un serveur) qui sont reconnues par le serveur comme provenant du 6 même client. Il est alors possible de partager des informations sur la totalité de la session (variables de session) qui peuvent être lues et modifiées à chaque requête. 3.1 Une première idée : les adresses IP Afin d’identifier un utilisateur pour suivre ses requêtes successives sur un même serveur, on pourrait vouloir utiliser l’adresse IP de l’ordinateur client. Notons que cette adresse IP n’est pas donnée dans la requête HTTP (elle ne figure pas dans les en-têtes) mais que le serveur connaît l’IP de l’ordinateur qui a demandé la connexion TCP/IP. Cette technique présente cependant de nombreux inconvénients : – Si le client utilise un proxy entre son ordinateur et le serveur HTTP, seule l’adresse du proxy sera visible par le serveur. Certains fournisseurs d’accès à Internet (AOL par exemple qui est le principal FAI aux États-Unis) font passer toutes leurs connexion par un petit nombre de proxys, ce qui empêche donc les serveurs web de les distinguer par leur adresse IP. – Un même ordinateur peut être partagé par différents utilisateurs. – Les ordinateurs sur Internet n’ont pas nécessairement une adresse IP fixe. La plupart des FAI aujourd’hui distribuent dynamiquement les adresses IP et il ne sera donc pas possible pour un serveur de reconnaître un utilisateur si l’ordinateur de ce dernier s’est déconnecté puis reconnecté avec une autre IP. Notons que de plus en plus de FAI proposent maintenant une option « IP fixe » à leurs clients. 3.2 La solution HTTP Il est possible de suivre les requêtes successives d’un même client en utilisant les méthodes GET et POST du protocole HTTP sur des pages web générées dynamiquement. En effet, nous avons vu que ces deux méthodes permettent de transmettre des variables lors d’une requête HTTP : dans le cas de POST les variables et leur valeur sont transmises dans le corps de la requête tandis que dans le cas de GET elles sont transmises dan l’URI (en ajoutant une chaîne de la forme ?nom1=val1&nom2=val2). Lorsqu’un utilisateur arrive sur une page sans fournir de variables dans sa requête le serveur le considère comme un nouvel utilisateur et lui attribue un numéro de session (différent des numéros déjà attribués à d’autres utilisateurs). Une fois ce numéro déterminé (qui servira à identifier cet utilisateur), le serveur modifie tous les liens des pages qu’il envoie au client pour que celui-ci envoie son numéro de session à chaque fois qu’il suit un lien. Si par exemple le numéro qui a été attribué est 1032, on ajoute ?id=1032 à tous les liens pour que l’identifiant soit passé au serveur par la méthode GET à chaque page demandée. Si l’on veut utiliser la méthode POST il suffit d’ajouter un formulaire caché (qui n’est pas affiché par la page) contenant un champ pré-rempli indiquant le numéro de session (ce formulaire sera donc transmis à chaque requête de la part du client). Il est ensuite facile pour le client d’identifier toutes les requêtes faites par le même client puisqu’elles contiennent toutes le même identifiant. Le serveur peut également modifier les valeurs (et le nombre) des variables que le client enverra, mais un seul identifiant pourrait suffire puisque toutes les variables nécessaires pourraient être enregistrées sur le serveur (toutefois le site pourrait vouloir ne pas encombrer la mémoire du serveur et donc transmettre les variables aux utilisateurs). 3.2.1 Différences entre POST et GET Lorsque l’on utilise la méthode GET les variables sont échangées de manière visible dans l’URI. Ceci peut être intéressant pour permettre à l’utilisateur de modifier facilement les valeurs des variables transmises ou de les échanger avec quelqu’un d’autre en copiant simplement l’URI. C’est ce qui permet également aux navigateurs de créér des marque-pages dynamiques : on peut marquer la page d’un moteur de recherche en indiquant où doit être insérée la chaîne de caractères à rechercher, ce qui permet d’effectuer une recherche directement sans avoir à passer par la page principale du moteur de recherche. 7 On pourra cependant préférer cacher ces variables à l’aide de la méthode POST pour différentes raisons. Ça allège les URI affichées par les navigateurs qui sont donc plus faciles à mémoriser et plus agréables pour l’utilisateur, c’est un moyen de faire passer des variables discrètement sans que l’utilisateur s’en rende compte (il pourrait le voir mais il est rare que l’on inspecte le code source de la page ou les requêtes HTTP transmises par le navigateur), et enfin ça permet de passer une plus grande quantité d’informations (certains serveurs refusent les URI trop longues, et il est en général déconseillé de dépasser 255 caractères). Enfin, remarquons que ces deux méthodes ne se comporteront pas de la même manière vis-à-vis des marque-pages. Dans le cas d’un passage de variables dans l’URI (GET) si l’utilisateur créé un marque page , copie entièrement l’adresse, ou l’envoie à ses contacts, les variables seront copiées également et donc ces mêmes variables (avec leurs valeurs) seront utilisées à chaque fois. Si l’on cache les variables (POST) elles ne seront pas mémorisées ou transmises. 3.2.2 Inconvénients Cette méthode permet donc d’implémenter un mécanismes de sessions (et de séquetialité des requêtes d’un client à un serveur) mais elle présente encore certains inconvénients : – Les sessions ne sont pas conservées si l’utilisateur quitte le site et revient en entrant la même adresse que la première fois qu’il est venu (donc sans transmettre de variable). – À l’inverse, comme nous l’avons vu, si l’utilisateur revient sur la page après avoir copié l’adresse les variables reprendront toutes la valeur qu’elles avaient au moment où l’adresse a été copiée (ou marquée dans un navigateur). – Si l’on utilise un proxy qui garde en mémoire les pages web les plus demandées, celui-ci va transmettre à tous les utilisateurs la même page qui ne sera donc pas recréée dynamiquement pour chacun. Toutes ces pages contiendront donc les mêmes variables et ne permettront pas de distinguer les utilisateurs. 3.3 Cookies HTTP Les cookies HTTP ont été introduits par le navigateur Netscape afin de pouvoir implémenter correctement un système de sessions. L’objectif était de permettre de gérer un panier de courses virtuel sur un site d’achats en ligne (l’utilisateur parcourt le site et peut à tout moment ajouter ou supprimer des objets de son panier virtuel afin de payer la totalité de la commande en une seule fois). En pratique, les cookies sont des chaînes de caractères que le serveur envoie au client dans l’en-tête d’une réponse HTTP. Ces cookies sont stockés sur l’ordinateur du client (en général dans un unique fichier de cookies). Par la suite, à chaque fois que le client enverra une requête à ce serveur, il lui transmettra le cookie dans l’en-tête de la requête HTTP. Le serveur a la possibilité de modifier ou supprimer le cookie, et peut ajouter des cookies supplémentaires. 3.3.1 Syntaxe côté serveur Pour placer un cookie chez le client, le serveur utilise le champ Set-Cookie dans l’en-tête de la réponse HTTP. La syntaxe est la suivante : Set-Cookie: NOM=VALEUR; expires=DATE; path=CHEMIN; domain=NOM_DE_DOMAINE; secure où les mots en minuscule désignent des mots-clés (imposés par le format des cookies) et les mots en majuscule sont à remplacer par les valeurs choisies par le serveur. Un cookie a donc plusieurs attributs : – NOM=VALEUR. C’est le seul champ qui doit obligatoirement être renseigné. Il définit le nom du cookie et sa valeur. Le nom et la valeur du cookie ne peuvent pas contenir de point virgule, de virgule ou d’espace. De plus le caractère ’=’ ne peut pas apparaître dans le nom puisque le premier ’=’ rencontré indique la séparation entre le nom et la valeur du cookie (mais la valeur peut contenir des caractères =). – expires=DATE. Ce champ indique la limite de validité du cookie. La date est représentée sous la forme 8 Wed, 06-Feb-2008 18:14:35 GMT où GMT est le seul fuseau horaire accepté. Souvent les navigateurs enregistrent cette date dans le fichier de cookies en la mesurant en secondes depuis l’epoch (1er janvier 1970) sous la forme d’un entier écrit en base 10 (par exemple 1202318075 pour la date indiquée précédemment). Lorsque le navigateur parcourt la liste des cookies disponibles, il supprime tous ceux qui sont périmés. C’est également ce mécanisme qui est utilisé par le serveur pour demander la suppression d’un cookie chez le client : il modifie la date d’expiration à une date quelconque dans le passé. Il n’est pas indispensable de fournir une date d’expiration. Si aucune n’est fournie le cookie n’est valable que pour la session courante et est donc détruit lors de la fermeture du navigateur. – domain=NOM_DE_DOMAINE. Indique le domaine de validité du cookie. Le navigateur ne transmettra le cookie que lorsque la requête HTTP est transmise à un sous-domaine de celui indiqué. Ainsi, si par exemple le navigateur reçoit un cookie de domaine domaine.com, il le transmettra à chaque fois qu’il contactera le serveur domaine.com, mais également s’il contacte mon.domaine.com ou moi.mon.domaine.com. Le serveur qui envoie le cookie doit nécessairement appartenir au domaine spécifié sans quoi le navigateur refuse le cookie. Par ailleurs il existe des contraintes sur le domaine d’un cookie qui imposent une certaine spécificité, ainsi il n’est pas possible de fixer un cookie dont le domaine est com ou co.uk qui sont trop génériques. Si le domaine n’est pas précisé, c’est le domaine du serveur qui est considéré par défaut. – path=CHEMIN. Cet attribut précise l’ensemble des URI qui sont concernées par le cookie. En effet le cookie ne sera transmis que si l’URI demandée commence par le contenu du champ path. Par exemple, si le chemin est /un/chemin, le cookie sera envoyé lorsque l’on demande le fichier /un/chemin/index.html, mais également si l’on demande /un/cheminot. Si le chemin n’est pas spécifié lorsque le cookie est envoyé il est considéré comme se rapportant au chemin « / » qui inclut toutes les pages du domaine. – secure. Cet attribut (qui n’a pas de valeur) indique simplement que le cookie ne doit être renvoyé par le navigateur que si la connexion utilisée est cryptée. Si cet argument n’est pas donné à la création du cookie, le cookie est envoyé quelle que soit la sécurité de la connexion. Cet attribut est utilisé lorsque le cookie représente une information sensible (une identification sur un site bancaire par exemple). 3.3.2 Syntaxe côté client Lorsque le client envoie une requête, le navigateur parcourt la liste des cookies et détermine ceux qui correspondent à la requête émise (la date d’expiration n’est pas passée, le domaine et le chemin correspondent, et la sécurité de la connexion est adaptée). Il transmet ensuite les noms et valeurs de chacun de ces cookies en une seule ligne de l’en-tête de la requête HTTP sous la forme : Cookie: NOM1=VALEUR1; NOM2=VALEUR2; ... 3.3.3 Remarques – Il est possible d’envoyer plusieurs cookies dans une unique réponse HTTP en utilisant plusieurs champs Set-Cookie dans l’en-tête. – Lorsqu’un cookie portant le même nom et correspondant aux mêmes domaine et chemin qu’un cookie existant est envoyé, il remplace le précédent. Si par contre les chemins sont différents les deux cookies sont conservés. – Le client peut choisir de détruire un cookie à tout moment (même si la date limite n’est pas atteinte). Inversement, il peut choisir de continuer à utiliser un cookie périmé. – Lorsque le client envoie les cookies au serveur, les cookies correspondant aux chemins les plus spécifiques doivent être envoyés en premier. Ceci est particulièrement important dans le cas où deux cookies de même nom mais de chemin différent co-existent. Ainsi si le client a deux cookies nom=val1 de chemin / et nom=val2 de chemin /blop/, le cookie nom=val2 doit apparaître en premier dans la liste envoyée. – Le nombre et la taille des cookies sont limités. Cette limite varie en fonction du navigateur. De manière générale, on considère les limites suivantes : – le navigateur ne conserve pas plus de 300 cookies au total ; 9 – la chaîne formée du nom et de la valeur d’un cookie ne doit pas dépasser 4096 caractères ; – seuls 20 cookies par domaine sont acceptés. – Les serveurs doivent considérer que les navigateurs clients ne dépasseront pas ces limites. Lorsqu’un cookie est reçu alors que la limite est atteinte le navigateur est censé supprimer le cookie dont la dernière utilisation est la plus ancienne. – Par ailleurs si le navigateur reçoit un cookie dont la taille dépasse 4096 caractères, il doit le tronquer en conservant le nom. – Les serveurs proxy qui utilisent un cache pour stocker les réponses HTTP ne doivent pas enregistrer les champs Set-Cookie. 10