Dans le dernier article, nous avons examiné les messages HTTP et avons vu des exemples de commandes de texte et de codes qui vont du client au serveur et inversement dans une transaction HTTP. Mais comment les informations contenues dans ces messages se déplacent-elles sur le réseau? Quand les connexions réseau sont-elles ouvertes? Quand les connexions sont-elles fermées? Voici quelques-unes des questions auxquelles cet article répondra lorsque nous examinerons HTTP dans une perspective de bas niveau. Mais d'abord, nous devons comprendre certaines des abstractions ci-dessous HTTP.
Pour comprendre les connexions HTTP, nous devons connaître un peu ce qui se passe dans les couches situées sous HTTP. La communication réseau, comme de nombreuses applications, est constituée de couches. Chaque couche dans un pile de communication est responsable d'un nombre spécifique et limité de responsabilités.
Par exemple, HTTP est ce que nous appelons un protocole de couche application car il permet à deux applications de communiquer sur un réseau. Très souvent, l’une des applications est un navigateur Web et l’autre application est un serveur Web comme IIS ou Apache. Nous avons vu comment les messages HTTP permettent au navigateur de demander des ressources au serveur. Cependant, les spécifications HTTP ne disent rien sur la façon dont les messages traversent le réseau et atteignent le serveur - c'est le travail des protocoles de couche inférieure. Un message d'un navigateur Web doit parcourir une série de couches et, lorsqu'il arrive au serveur Web, il parcourt une série de couches pour atteindre le processus de service Web..
Couches de protocoleLa couche sous HTTP est un protocole de couche de transport. Presque tout le trafic HTTP transite par TCP (abréviation de Transmission Control Protocol), bien que cela ne soit pas requis par HTTP. Lorsqu'un utilisateur tape une URL dans le navigateur, celui-ci extrait d'abord le nom d'hôte de l'URL (et le numéro de port, le cas échéant), puis ouvre une fenêtre. Prise TCP en spécifiant l'adresse du serveur (dérivée du nom d'hôte) et le port (80 par défaut).
Une fois qu'une application a un socket ouvert, elle peut commencer à écrire des données dans le socket. La seule chose dont le navigateur doit s’inquiéter est d’écrire un message de requête HTTP correctement formaté dans le socket. La couche TCP accepte les données et veille à ce que le message soit remis au serveur sans être perdu ni dupliqué. TCP renverra automatiquement toute information qui pourrait se perdre en transit. C’est pourquoi TCP est connu sous le nom de protocole fiable. En plus de la détection d'erreur, TCP fournit également un contrôle de flux. Les algorithmes de contrôle de flux dans TCP garantissent que l'expéditeur n'envoie pas de données trop rapidement pour que le destinataire puisse les traiter. Le contrôle de flux est important dans ce monde de réseaux et d'appareils variés.
En bref, TCP fournit des services essentiels au bon acheminement des messages HTTP, mais de manière transparente, de sorte que la plupart des applications n’ont pas à s’inquiéter de TCP. Comme le montre la figure précédente, TCP n'est que la première couche sous HTTP. Après TCP à la couche de transport vient IP comme protocole de couche réseau.
IP est l'abréviation de protocole Internet. Alors que TCP est responsable de la détection des erreurs, du contrôle des flux et de la fiabilité globale, IP est responsable de la collecte des informations et de leur transfert à travers les divers commutateurs, routeurs, passerelles, répéteurs et autres périphériques permettant le transfert des informations d’un réseau à un autre. partout dans le monde. IP tente de livrer les données à la destination (mais cela ne garantit pas la livraison, c'est le travail de TCP). IP nécessite que les ordinateurs aient une adresse (la célèbre adresse IP, par exemple 208.192.32.40). IP est également responsable de la décomposition des données en paquets (souvent appelés datagrammes), et parfois de la fragmentation et du réassemblage de ces paquets afin qu'ils soient optimisés pour un segment de réseau particulier..
Tout ce dont nous avons parlé jusqu'à présent se passe à l'intérieur d'un ordinateur, mais ces paquets IP doivent finalement voyager sur un fil, un câble à fibre optique, un réseau sans fil ou une liaison par satellite. C’est la responsabilité du couche de liaison de données. Un choix commun de technologie à ce stade est Ethernet. À ce niveau, les paquets de données deviennent des trames et les protocoles de bas niveau tels qu'Ethernet sont axés sur les 1, les 0 et les signaux électriques..
Finalement, le signal atteint le serveur et entre par une carte réseau où le processus est inversé. La couche liaison de données envoie des paquets à la couche IP, qui transfère les données à TCP, qui peut ensuite réassembler les données dans le message HTTP d'origine envoyé par le client et les transférer dans le processus du serveur Web. C'est un travail superbement conçu, rendu possible par les standards.
Si vous vous demandez à quoi ressemble une application capable de générer des requêtes HTTP, le code C # ci-dessous est un exemple simple de ce à quoi il pourrait ressembler. Ce code ne gère aucune erreur et tente d'écrire une réponse du serveur dans la fenêtre de la console (vous devez donc demander une ressource textuelle), mais cela fonctionne pour des requêtes simples. Une copie de l'exemple de code suivant est disponible à l'adresse https://bitbucket.org/syncfusion/http-succinctly. Le nom de l'échantillon est sockets-sample.
en utilisant le système; using System.Net; using System.Net.Sockets; using System.Text; classe publique GetSocket vide statique public Main (string [] args) var host = "www.wikipedia.org"; var ressource = "/"; Console.WriteLine ("Connexion à 0", hôte); if (args.GetLength (0)> = 2) hôte = args [0]; ressource = args [1]; var result = GetResource (hôte, ressource); Console.WriteLine (result); chaîne statique privée GetResource (chaîne hôte, ressource chaîne) var hostEntry = Dns.GetHostEntry (hôte); var socket = CreateSocket (hostEntry); SendRequest (socket, hôte, ressource); retourne GetResponse (socket); Socket statique privé CreateSocket (IPHostEntry hostEntry) const int httpPort = 80; foreach (adresse var dans hostEntry.AddressList) var endPoint = new IPEndPoint (address, httpPort); var socket = new Socket (endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); socket.Connect (endPoint); if (socket.Connected) return socket; return null; void statique privé SendRequest (socket, hôte de chaîne, ressource de chaîne) var requestMessage = String.Format ("GET 0 HTTP / 1.1 \ r \ n" + "hôte: 1 \ r \ n" + " \ r \ n ", ressource, hôte); var requestBytes = Encodage.ASCII.GetBytes (requestMessage); socket.Send (requestBytes); Chaîne statique privée GetResponse (Socket socket) int bytes = 0; octet [] tampon = nouvel octet [256]; var result = new StringBuilder (); do octets = socket.Receive (tampon); result.Append (Encoding.ASCII.GetString (tampon, 0, octets)); while (octets> 0); renvoyer le résultat.ToString ();
Notez que le programme doit rechercher l’adresse du serveur (en utilisant Dns.GetHostEntry
) et formuler un message HTTP approprié avec un OBTENIR
opérateur et Hôte
entête. La partie réseau proprement dite est assez facile, car l’implémentation du socket et le protocole TCP s’occupent de la majeure partie du travail. TCP comprend, par exemple, comment gérer plusieurs connexions sur le même serveur (ils recevront tous des numéros de port différents localement). De ce fait, deux demandes en attente adressées au même serveur ne seront pas confondues et ne recevront pas les données destinées à l'autre..
Si vous voulez un peu de visibilité sur TCP et IP, vous pouvez installer un programme gratuit tel que Wireshark (disponible pour OSX et Windows sur Wirehark.org). Wireshark est un analyseur de réseau capable de vous montrer toutes les informations transitant par vos interfaces réseau. En utilisant Wireshark, vous pouvez observer les liaisons TCP, qui sont les messages TCP nécessaires pour établir une connexion entre le client et le serveur avant que les messages HTTP réels ne commencent à circuler. Vous pouvez également voir les en-têtes TCP et IP (20 octets chacun) sur chaque message. La figure suivante montre les deux dernières étapes de la poignée de main, suivie d'un OBTENIR
demande et un 304
réorienter.
Avec Wireshark, vous pouvez voir quand les connexions HTTP sont établies et fermées. La partie importante à retenir de tout cela n’est pas la manière dont la poignée de main et le protocole TCP fonctionnent au niveau le plus bas, mais le fait que HTTP repose presque entièrement sur TCP pour s’acquitter de tout le travail ardu et que TCP implique une surcharge, comme une poignée de main. Ainsi, les caractéristiques de performance de HTTP reposent également sur les caractéristiques de performance de TCP. C’est le sujet de la section suivante..
À l’époque très ancienne du Web, la plupart des ressources étaient textuelles. Vous pouvez demander un document à un serveur Web, partir et lire pendant cinq minutes, puis demander un autre document. Le monde était simple.
Pour le Web d'aujourd'hui, la plupart des pages Web requièrent plus d'un rendu complet. Chaque page d'une application Web contient une ou plusieurs images, un ou plusieurs fichiers JavaScript et un ou plusieurs fichiers CSS. Il n'est pas rare que la demande initiale d'une page d'accueil génère 30 ou 50 demandes supplémentaires pour récupérer toutes les autres ressources associées à une page..
Autrefois, il était également simple pour un navigateur d'établir une connexion avec un serveur, d'envoyer une demande, de recevoir la réponse et de fermer la connexion. Si les navigateurs Web actuels ouvraient les connexions une par une et attendaient que chaque ressource soit entièrement téléchargée avant de commencer le téléchargement suivant, le Web se sentirait très lent. Internet est plein de latence. Les signaux doivent parcourir de longues distances et se frayer un chemin à travers différents éléments matériels. L'établissement d'une connexion TCP entraîne également une surcharge. Comme nous l'avons vu dans la capture d'écran de Wireshark, il faut une poignée de main en trois étapes avant qu'une transaction HTTP puisse commencer.
L’évolution des simples documents aux pages complexes a nécessité une certaine ingéniosité dans l’utilisation pratique de HTTP.
La plupart des agents utilisateurs (ou navigateurs Web) ne feront pas de requêtes en série, une par une. Au lieu de cela, ils ouvrent plusieurs connexions parallèles à un serveur. Par exemple, lors du téléchargement du code HTML d’une page, le navigateur peut voir deux balises dans la page, le navigateur ouvrira donc deux connexions parallèles pour télécharger les deux images simultanément. Le nombre de connexions parallèles dépend de l'agent d'utilisateur et de la configuration de l'agent..
Pendant longtemps, nous avons considéré deux comme le nombre maximal de connexions parallèles qu'un navigateur créerait. Nous avons considéré deux comme le maximum car le navigateur le plus populaire depuis de nombreuses années - Internet Explorer (IE) 6 - ne permettait que deux connexions simultanées à un seul hôte. IE ne faisait qu'obéir aux règles énoncées dans la spécification HTTP 1.1, qui stipule:
Un client mono-utilisateur NE DEVRAIT PAS maintenir plus de 2 connexions avec n’importe quel serveur ou proxy..
Pour augmenter le nombre de téléchargements parallèles, de nombreux sites Web utilisent certaines astuces. Par exemple, la limite de deux connexions est par hôte, Cela signifie qu'un navigateur comme IE 6 établirait deux connexions parallèles avec www.odetocode.com, et deux connexions parallèles à images.odetocode.com. En hébergeant des images sur un serveur différent, les sites Web pourraient augmenter le nombre de téléchargements parallèles et accélérer le chargement de leurs pages (même si les enregistrements DNS étaient configurés pour pointer les quatre demandes sur le même serveur, car la limite de deux connexions est actuellement définie. par nom d'hôte, pas d'adresse IP).
Les choses sont différentes aujourd'hui. La plupart des agents utilisateurs utiliseront un ensemble d'heuristiques différent pour décider du nombre de connexions parallèles à établir. Par exemple, Internet Explorer 8 va maintenant ouvrir jusqu'à six connexions simultanées..
La vraie question à poser est la suivante: combien de connexions sont trop nombreuses? Les connexions parallèles obéiront à la loi des rendements décroissants. Trop de connexions peuvent saturer et encombrer le réseau, en particulier lorsque des appareils mobiles ou des réseaux peu fiables sont impliqués. Ainsi, avoir trop de connexions peut nuire aux performances. En outre, un serveur ne peut accepter qu'un nombre fini de connexions. Par conséquent, si 100 000 navigateurs créent simultanément 100 connexions sur un seul serveur Web, des problèmes peuvent se produire. Néanmoins, il est préférable d’utiliser plusieurs connexions par agent plutôt que de tout télécharger en série..
Heureusement, les connexions parallèles ne sont pas la seule optimisation des performances.
Dans les premiers jours du Web, un agent d'utilisateur ouvrait et fermait une connexion pour chaque requête individuelle envoyée à un serveur. Cette implémentation était conforme à l'idée de HTTP d'être un protocole complètement sans état. À mesure que le nombre de demandes par page augmentait, la surcharge générée par les liaisons TCP et les structures de données en mémoire requises pour établir chaque socket TCP. Pour réduire cette surcharge et améliorer les performances, la spécification HTTP 1.1 suggère que les clients et les serveurs doivent implémenter connexions persistantes, et faire des connexions persistantes le type de connexion par défaut.
Une connexion persistante reste ouverte après l'achèvement d'une transaction demande-réponse. Ce comportement laisse un agent utilisateur avec un socket déjà ouvert qu'il peut utiliser pour continuer à envoyer des requêtes au serveur sans la surcharge liée à l'ouverture d'un nouveau socket. Les connexions persistantes évitent également la stratégie de démarrage lent faisant partie du contrôle de la congestion TCP, améliorant ainsi les performances des connexions persistantes dans le temps. En bref, les connexions persistantes réduisent l'utilisation de la mémoire, l'utilisation du processeur, la congestion du réseau, la latence et améliorent généralement le temps de réponse d'une page. Mais, comme tout dans la vie, il y a un inconvénient.
Comme mentionné précédemment, un serveur ne peut prendre en charge qu'un nombre fini de connexions entrantes. Le nombre exact dépend de la quantité de mémoire disponible, de la configuration du logiciel serveur, des performances de l'application et de nombreuses autres variables. Il est difficile de donner un nombre exact, mais en règle générale, si vous parlez de prendre en charge des milliers de connexions simultanées, vous devrez commencer à tester pour voir si un serveur supportera la charge. En fait, de nombreux serveurs sont configurés pour limiter le nombre de connexions simultanées bien en dessous du point de basculement du serveur. La configuration est une mesure de sécurité permettant d'empêcher les attaques par déni de service. Il est relativement facile pour quelqu'un de créer un programme qui ouvrira des milliers de connexions persistantes à un serveur et empêchera celui-ci de répondre à de vrais clients. Les connexions persistantes sont une optimisation des performances mais également une vulnérabilité.
En pensant aux vulnérabilités, nous devons également nous demander combien de temps pour garder une connexion persistante ouverte. Dans un monde caractérisé par une évolutivité infinie, les connexions peuvent rester ouvertes aussi longtemps que le programme d'agent d'utilisateur est en cours d'exécution. Toutefois, comme un serveur prend en charge un nombre fini de connexions, la plupart des serveurs sont configurés pour fermer une connexion persistante si elle est inactive pendant un certain temps (cinq secondes sous Apache, par exemple). Les agents d'utilisateur peuvent également fermer les connexions après une période d'inactivité. La seule visibilité possible sur les connexions fermées est un analyseur de réseau tel que Wireshark..
Outre la fermeture agressive des connexions persistantes, la plupart des logiciels de serveur Web peuvent être configurés pour désactiver les connexions persistantes. Ceci est commun avec les serveurs partagés. Les serveurs partagés sacrifient les performances pour permettre autant de connexions que possible. Les connexions persistantes étant le style de connexion par défaut avec HTTP 1.1, un serveur qui n'autorise pas les connexions persistantes doit inclure une Lien
en-tête dans chaque réponse HTTP. Le code suivant est un exemple.
HTTP / 1.1 200 OK Type de contenu: text / html; charset = utf-8 Serveur: Microsoft-IIS / 7.0 X-AspNet-Version: 2.0.50727 X-Powered-By: ASP.NET Connexion: close Content-Length: 17149
le Connexion: fermer
header est un signal à l'agent utilisateur que la connexion ne sera pas persistante et doit être fermée dès que possible. L'agent n'est pas autorisé à faire une deuxième demande sur la même connexion.
Les connexions parallèles et les connexions persistantes sont largement utilisées et prises en charge par les clients et les serveurs. La spécification HTTP permet également de connexions en pipeline, qui ne sont pas aussi largement supportés par les serveurs ou les clients. Dans une connexion en pipeline, un agent d'utilisateur peut envoyer plusieurs demandes HTTP sur une connexion avant d'attendre la première réponse. Le traitement en pipeline permet un regroupement plus efficace des demandes en paquets et peut réduire le temps de latence, mais il n'est pas aussi largement pris en charge que les connexions parallèles et persistantes..
Dans ce chapitre, nous avons examiné les connexions HTTP et évoqué certaines des optimisations de performances rendues possibles par les spécifications HTTP. Maintenant que nous avons approfondi nos messages HTTP et que nous avons même examiné les connexions et la prise en charge de TCP sous le protocole, nous allons prendre du recul et examiner Internet dans une perspective plus large..