Guide Node.js, partie 8: protocoles HTTP et WebSocket

Node.js est une plate-forme serveur. La tĂąche principale du serveur est de traiter les demandes des clients, en particulier des navigateurs, aussi rapidement et efficacement que possible. Le huitiĂšme de la traduction du didacticiel Node.js que nous publions aujourd'hui concerne HTTP et WebSocket.




Que se passe-t-il lors des requĂȘtes HTTP?


Parlons de la façon dont les navigateurs font des requĂȘtes aux serveurs en utilisant le protocole HTTP / 1.1.

Si vous avez dĂ©jĂ  eu une interview dans le domaine informatique, il se peut que l'on vous demande ce qui se passe lorsque vous tapez quelque chose dans la barre d'adresse de votre navigateur et appuyez sur EntrĂ©e. C'est peut-ĂȘtre l'une des questions les plus populaires qui se posent lors de ces entretiens. Quiconque pose de telles questions veut savoir si vous pouvez expliquer des concepts assez simples et savoir si vous comprenez les principes d'Internet.

Cette question touche à de nombreuses technologies, pour comprendre les principes généraux de ce qui signifie comprendre comment est construit l'un des systÚmes les plus complexes jamais construits par l'humanité, qui couvre le monde entier.

▍ Protocole HTTP


Les navigateurs modernes sont capables de distinguer les URL rĂ©elles entrĂ©es dans leur barre d'adresse des requĂȘtes de recherche, pour le traitement desquelles le moteur de recherche par dĂ©faut est gĂ©nĂ©ralement utilisĂ©. Nous parlerons des URL. Si vous entrez une adresse de site Web, telle que flaviocopes.com , dans la ligne du navigateur, le navigateur convertit cette adresse au flaviocopes.com http://flaviocopes.com , en supposant que le protocole HTTP sera utilisĂ© pour Ă©changer des donnĂ©es avec la ressource spĂ©cifiĂ©e. Veuillez noter que sous Windows, ce dont nous allons parler ici peut sembler un peu diffĂ©rent de celui sous macOS et Linux.

▍ Phase de recherche DNS


Ainsi, le navigateur, en commençant à télécharger des données à partir de l'adresse demandée par les utilisateurs, effectue l'opération de recherche DNS (DNS Lookup) afin de trouver l'adresse IP du serveur correspondant. Les noms symboliques des ressources saisies dans la barre d'adresse conviennent aux utilisateurs, mais le périphérique Internet implique la possibilité d'échanger des données entre des ordinateurs à l'aide d'adresses IP, qui sont des ensembles de nombres comme 222.324.3.1 (pour IPv4).

Tout d'abord, en découvrant l'adresse IP du serveur, le navigateur examine le cache DNS local pour voir si une procédure similaire a été effectuée récemment. Dans le navigateur Chrome, par exemple, il existe un moyen pratique de consulter le cache DNS en entrant l'adresse suivante dans la barre d'adresse: chrome://net-internals/#dns .

Si rien ne se trouve dans le cache, le navigateur utilise l'appel systĂšme POSIX gethostbyname pour connaĂźtre l'adresse IP du serveur.

▍ fonction gethostbyname


La fonction gethostbyname vĂ©rifie d'abord le hosts , qui, sur macOS ou Linux, peut ĂȘtre trouvĂ© dans /etc/hosts afin de savoir si des informations locales peuvent ĂȘtre trouvĂ©es en recherchant l'adresse du serveur.

Si des moyens locaux pour résoudre la demande de découverte de l'adresse IP du serveur échouent, le systÚme effectue une demande au serveur DNS. Les adresses de ces serveurs sont stockées dans les paramÚtres systÚme.

Voici quelques serveurs DNS populaires:

  • 8.8.8.8: serveur DNS Google.
  • 1.1.1.1: Serveur DNS CloudFlare.

La plupart des gens utilisent les serveurs DNS fournis par leurs fournisseurs. Le navigateur effectue des requĂȘtes DNS Ă  l'aide du protocole UDP.

TCP et UDP sont deux protocoles de base utilisĂ©s dans les rĂ©seaux informatiques. Ils sont situĂ©s au mĂȘme niveau conceptuel, mais TCP est un protocole orientĂ© connexion, et pour l'Ă©change de messages UDP, dont le traitement crĂ©e une petite charge supplĂ©mentaire sur le systĂšme, une procĂ©dure d'Ă©tablissement de connexion n'est pas nĂ©cessaire. Nous ne parlerons pas exactement de la façon dont les donnĂ©es sont Ă©changĂ©es via UDP.

L'adresse IP correspondant au nom de domaine qui nous intĂ©resse peut ĂȘtre dans le cache du serveur DNS. Si ce n'est pas le cas, il contactera le serveur DNS racine. Le systĂšme de serveur DNS racine se compose de 13 serveurs, dont dĂ©pend le fonctionnement de l'ensemble de l'Internet.

Il convient de noter que le serveur DNS racine ne connaßt pas la correspondance entre tous les noms de domaine et adresses IP existants dans le monde. Mais des serveurs similaires connaissent les adresses des serveurs DNS de niveau supérieur pour des domaines tels que .com, .it, .pizza, etc.

DÚs réception de la demande, le serveur DNS racine la redirige vers le serveur DNS du domaine de premier niveau, vers le serveur dit TLD (à partir du domaine de premier niveau).

Supposons que le navigateur recherche l'adresse IP du serveur flaviocopes.com . En ce qui concerne le serveur DNS racine, le navigateur recevra de celui-ci l'adresse du serveur TLD pour la zone .com. Maintenant, cette adresse sera stockée dans le cache, par conséquent, si vous avez besoin de trouver l'adresse IP d'une autre URL à partir de la zone .com, vous n'aurez plus à accéder au serveur DNS racine.

Les serveurs TLD ont des adresses IP de serveurs de noms (Name Server, NS), Ă  l'aide desquels vous pouvez trouver l'adresse IP Ă  partir de l'URL que nous avons. OĂč le serveur NS obtient-il ces informations? Le fait est que si vous achetez un domaine, le registraire de domaine envoie des donnĂ©es Ă  ce sujet aux serveurs de noms. Une procĂ©dure similaire est effectuĂ©e, par exemple, lors du changement d'hĂ©bergement.

Les serveurs en question appartiennent généralement à des hébergeurs. En rÚgle générale, pour se protéger contre les pannes, plusieurs de ces serveurs sont créés. Par exemple, ils peuvent avoir ces adresses:

  • ns1.dreamhost.com
  • ns2.dreamhost.com
  • ns3.dreamhost.com

Pour connaßtre l'adresse IP par URL, ils se tournent finalement vers ces serveurs. Ils stockent les données réelles sur les adresses IP.

Maintenant, aprÚs avoir réussi à trouver l'adresse IP derriÚre l'URL entrée dans la barre d'adresse du navigateur, nous passons à l'étape suivante de notre travail.

▍ Etablissement d'une connexion TCP


AprÚs avoir appris l'adresse IP du serveur, le client peut établir une connexion TCP avec lui. Lors de l'établissement d'une connexion TCP, le client et le serveur se transmettent mutuellement des données de service, aprÚs quoi ils peuvent échanger des informations. Cela signifie qu'une fois la connexion établie, le client pourra envoyer une demande au serveur.

▍Envoyer une demande


Une demande est un fragment de texte structuré conformément aux rÚgles du protocole utilisé. Il se compose de trois parties:

  • ChaĂźne de requĂȘte
  • En-tĂȘte de demande.
  • Demander le corps.

ChaĂźne de requĂȘte


La chaĂźne de requĂȘte est une chaĂźne de texte unique qui contient les informations suivantes:

  • MĂ©thode HTTP.
  • Adresse de la ressource
  • Version du protocole.

Cela peut ressembler, par exemple, Ă  ceci:

 GET / HTTP/1.1 

En-tĂȘte de demande


L'en-tĂȘte de demande est reprĂ©sentĂ© par un ensemble de : . Il y a 2 champs d'en-tĂȘte obligatoires, dont l'un est Host et le second est Connection . Les champs restants sont facultatifs.

Le titre peut ressembler Ă  ceci:

 Host: flaviocopes.com Connection: close 

Le champ Host indique le nom de domaine qui intĂ©resse le navigateur. Le champ Connection , dĂ©fini sur close , signifie que la connexion entre le client et le serveur n'a pas besoin d'ĂȘtre maintenue ouverte.

Les autres en-tĂȘtes de demande couramment utilisĂ©s sont les suivants:

  • Origin
  • Accept
  • Accept-Encoding
  • Cookie
  • Cache-Control
  • Dnt

En fait, il y en a beaucoup plus.

L'en-tĂȘte de demande se termine par une chaĂźne vide.

Organe de demande


Le corps de la demande est facultatif; il n'est pas utilisĂ© dans les demandes GET. Le corps de la requĂȘte est utilisĂ© dans les requĂȘtes POST, ainsi que dans d'autres requĂȘtes. Il peut contenir, par exemple, des donnĂ©es au format JSON.

Puisque nous parlons maintenant d'une requĂȘte GET, le corps de la requĂȘte sera vide, nous ne travaillerons pas avec.

▍ RĂ©pondre


Une fois que le serveur a reçu la demande envoyée par le client, il la traite et envoie une réponse au client.

La réponse commence par un code d'état et un message correspondant. Si la demande aboutit, le début de la réponse ressemblera à ceci:

 200 OK 

En cas de problÚme, il peut y avoir d'autres codes. Par exemple, les éléments suivants:

  • 404 Not Found
  • 403 Forbidden
  • 301 Moved Permanently
  • 500 Internal Server Error
  • 304 Not Modified
  • 401 Unauthorized

De plus, la rĂ©ponse contient une liste d'en-tĂȘtes HTTP et le corps de la rĂ©ponse (qui, puisque la demande est exĂ©cutĂ©e par le navigateur, sera du code HTML).

Analyse HTML


Une fois que le navigateur a reçu la réponse du serveur, dont le corps contient du code HTML, il commence à l'analyser, en répétant le processus ci-dessus pour chaque ressource nécessaire pour former la page. Ces ressources comprennent, par exemple, les éléments suivants:

  • Fichiers CSS.
  • Les images
  • IcĂŽne de page Web (favicon).
  • Fichiers JavaScript.

La façon exacte dont le navigateur affiche la page ne s'applique pas à notre conversation. La principale chose qui nous intéresse ici est que le processus de demande et de réception de données ci-dessus est utilisé non seulement pour le code HTML, mais aussi pour tout autre objet transféré du serveur au navigateur à l'aide du protocole HTTP.

À propos de la crĂ©ation d'un serveur simple Ă  l'aide de Node.js


Maintenant, aprÚs avoir examiné le processus d'interaction entre le navigateur et le serveur, vous pouvez jeter un nouveau regard sur la section d'application First Node.js de la premiÚre partie de cette série de documents, dans laquelle nous avons décrit le code d'un serveur simple.

Faire des requĂȘtes HTTP avec Node.js


Pour effectuer des requĂȘtes HTTP Ă  l'aide de Node.js, le module appropriĂ© est utilisĂ© . Les exemples ci-dessous utilisent le module https . Le fait est que dans les conditions modernes, dans la mesure du possible, il est nĂ©cessaire d'utiliser le protocole HTTPS.

▍ ExĂ©cution des requĂȘtes GET


Voici un exemple d'exécution d'une demande GET à l'aide de Node.js:

 const https = require('https') const options = { hostname: 'flaviocopes.com', port: 443, path: '/todos', method: 'GET' } const req = https.request(options, (res) => { console.log(`statusCode: ${res.statusCode}`) res.on('data', (d) => {   process.stdout.write(d) }) }) req.on('error', (error) => { console.error(error) }) req.end() 

ExecutionExĂ©cution de la requĂȘte POST


Voici comment effectuer une demande POST à ​​partir de Node.js:

 const https = require('https') const data = JSON.stringify({ todo: 'Buy the milk' }) const options = { hostname: 'flaviocopes.com', port: 443, path: '/todos', method: 'POST', headers: {   'Content-Type': 'application/json',   'Content-Length': data.length } } const req = https.request(options, (res) => { console.log(`statusCode: ${res.statusCode}`) res.on('data', (d) => {   process.stdout.write(d) }) }) req.on('error', (error) => { console.error(error) }) req.write(data) req.end() 

UTPUT et DELETE requĂȘtes


L'exĂ©cution de ces requĂȘtes ressemble Ă  celle des requĂȘtes POST. La principale diffĂ©rence, outre le contenu sĂ©mantique de ces opĂ©rations, est la valeur de la propriĂ©tĂ© method de l'objet options .

▍ ExĂ©cution de requĂȘtes HTTP dans Node.js Ă  l'aide de la bibliothĂšque Axios


Axios est une bibliothĂšque JavaScript trĂšs populaire qui fonctionne Ă  la fois dans le navigateur (cela inclut tous les navigateurs modernes et IE, Ă  commencer par IE8), et dans l'environnement Node.js, qui peut ĂȘtre utilisĂ© pour effectuer des requĂȘtes HTTP.

Cette bibliothÚque est basée sur des promesses, elle présente certains avantages par rapport aux mécanismes standard, en particulier par rapport à l'API Fetch. Ses avantages sont les suivants:

  • Prise en charge des anciens navigateurs (vous avez besoin d'un polyfill pour utiliser Fetch).
  • PossibilitĂ© d'interrompre les demandes.
  • Prise en charge de la dĂ©finition des dĂ©lais d'expiration des demandes.
  • Protection intĂ©grĂ©e contre les attaques CSRF.
  • Prise en charge du tĂ©lĂ©chargement de donnĂ©es avec la fourniture d'informations sur l'avancement de ce processus.
  • Prise en charge de la conversion de donnĂ©es JSON.
  • Emplois chez Node.js

L'installation


Vous pouvez utiliser npm pour installer Axios:

 npm install axios 

Le mĂȘme effet peut ĂȘtre obtenu avec du fil:

 yarn add axios 

Vous pouvez connecter la bibliothĂšque Ă  la page en utilisant unpkg.com :

 <script src="https://unpkg.com/axios/dist/axios.min.js"></script> 

API Axios


Vous pouvez faire une requĂȘte HTTP en utilisant l'objet axios :

 axios({ url: 'https://dog.ceo/api/breeds/list/all', method: 'get', data: {   foo: 'bar' } }) 

Mais il est généralement plus pratique d'utiliser des méthodes spéciales:

  • axios.get()
  • axios.post()

Ceci est similaire à la façon dont jQuery utilise $.get() et $.post() au lieu de $.ajax() $.post() .

Axios propose des mĂ©thodes distinctes pour exĂ©cuter d'autres types de requĂȘtes HTTP, qui ne sont pas aussi populaires que GET et POST, mais sont toujours utilisĂ©es:

  • axios.delete()
  • axios.put()
  • axios.patch()
  • axios.options()

La bibliothĂšque dispose d'une mĂ©thode pour exĂ©cuter une demande conçue pour recevoir uniquement des en-tĂȘtes HTTP, sans le corps de la rĂ©ponse:

  • axios.head()

GET demandes


Axios est pratique à utiliser en utilisant la syntaxe asynchrone / attente moderne. L'exemple de code suivant, conçu pour Node.js, utilise la bibliothÚque pour charger une liste de races de chiens à partir de l'API Dog . Ici, la méthode axios.get() est appliquée et les roches sont comptées:

 const axios = require('axios') const getBreeds = async () => { try {   return await axios.get('https://dog.ceo/api/breeds/list/all') } catch (error) {   console.error(error) } } const countBreeds = async () => { const breeds = await getBreeds() if (breeds.data.message) {   console.log(`Got ${Object.entries(breeds.data.message).length} breeds`) } } countBreeds() 

Le mĂȘme peut ĂȘtre réécrit sans utiliser async / wait, en appliquant des promesses:

 const axios = require('axios') const getBreeds = () => { try {   return axios.get('https://dog.ceo/api/breeds/list/all') } catch (error) {   console.error(error) } } const countBreeds = async () => { const breeds = getBreeds()   .then(response => {     if (response.data.message) {       console.log(         `Got ${Object.entries(response.data.message).length} breeds`       )     }   })   .catch(error => {     console.log(error)   }) } countBreeds() 

Utilisation de paramĂštres dans les requĂȘtes GET


Une requĂȘte GET peut contenir des paramĂštres qui ressemblent Ă  ceci dans une URL:

 https://site.com/?foo=bar 

Lorsque vous utilisez Axios, une requĂȘte de ce type peut ĂȘtre effectuĂ©e comme suit:

 axios.get('https://site.com/?foo=bar') 

Le mĂȘme effet peut ĂȘtre obtenu en dĂ©finissant la propriĂ©tĂ© params dans un objet avec des paramĂštres:

 axios.get('https://site.com/', { params: {   foo: 'bar' } }) 

Demandes POST


L'exĂ©cution des requĂȘtes POST est trĂšs similaire Ă  l'exĂ©cution des requĂȘtes GET, mais ici, au lieu de la mĂ©thode axios.get() , la mĂ©thode axios.post() est utilisĂ©e:

 axios.post('https://site.com/') 

Comme deuxiĂšme argument, la mĂ©thode post accepte un objet avec des paramĂštres de requĂȘte:

 axios.post('https://site.com/', { foo: 'bar' }) 

Utilisation du protocole WebSocket dans Node.js


WebSocket est une alternative Ă  HTTP, il peut ĂȘtre utilisĂ© pour organiser l'Ă©change de donnĂ©es dans des applications Web. Ce protocole vous permet de crĂ©er des canaux de communication bidirectionnels de longue durĂ©e entre le client et le serveur. Une fois la connexion Ă©tablie, le canal de communication reste ouvert, ce qui met l'application Ă  la disposition d'une connexion trĂšs rapide, caractĂ©risĂ©e par de faibles latences et une faible charge supplĂ©mentaire sur le systĂšme.

Le protocole WebSocket est pris en charge par tous les navigateurs modernes.

▍ DiffĂ©rences HTTP


HTTP et WebSocket sont des protocoles trĂšs diffĂ©rents qui utilisent diffĂ©rentes approches pour l'Ă©change de donnĂ©es. HTTP est basĂ© sur le modĂšle «request-response»: le serveur envoie des donnĂ©es au client aprĂšs leur demande. Dans le cas de WebSocket, tout est organisĂ© diffĂ©remment. À savoir:

  • Le serveur peut envoyer des messages au client de sa propre initiative, sans attendre une demande du client.
  • Le client et le serveur peuvent Ă©changer des donnĂ©es en mĂȘme temps.
  • Lors de la transmission d'un message, une quantitĂ© extrĂȘmement faible de donnĂ©es de service est utilisĂ©e. Cela conduit notamment Ă  une faible latence dans la transmission des donnĂ©es.

Le protocole WebSocket est trĂšs bien adaptĂ© aux communications en temps rĂ©el sur des canaux qui restent ouverts pendant longtemps. HTTP, Ă  son tour, est excellent pour organiser des sessions de communication occasionnelles initiĂ©es par le client. Dans le mĂȘme temps, il convient de noter que, d'un point de vue de la programmation, il est beaucoup plus facile de mettre en Ɠuvre l'Ă©change de donnĂ©es en utilisant le protocole HTTP qu'en utilisant le protocole WebSocket.

▍ Version protĂ©gĂ©e du protocole WebSocket


Il existe une version non sĂ©curisĂ©e du protocole WebSocket (schĂ©ma ws:// URI), qui ressemble, en termes de sĂ©curitĂ©, au protocole http:// . L'utilisation de ws:// doit ĂȘtre Ă©vitĂ©e, prĂ©fĂ©rant une version sĂ©curisĂ©e du protocole - wss:// .

▍CrĂ©ation d'une connexion WebSocket


Pour créer une connexion WebSocket, vous devez utiliser le constructeur approprié:

 const url = 'wss://myserver.com/something' const connection = new WebSocket(url) 

Une fois la connexion établie, l'événement open est déclenché. Vous pouvez organiser cet événement en affectant une fonction de rappel à la propriété onopen de l'objet de connection :

 connection.onopen = () => { //... } 

Pour gérer les erreurs, le onerror événements onerror est utilisé:

 connection.onerror = error => { console.log(`WebSocket error: ${error}`) } 

▍Envoyer des donnĂ©es au serveur


AprĂšs avoir ouvert une connexion WebSocket au serveur, vous pouvez lui envoyer des donnĂ©es. Cela peut ĂȘtre fait, par exemple, dans le onopen onopen:

 connection.onopen = () => { connection.send('hey') } 

▍ Obtention des donnĂ©es du serveur


Pour recevoir des données envoyées à l'aide du protocole WebSocket du serveur, vous pouvez affecter le onmessage onmessage, qui sera appelé lorsque l'événement de message est reçu:

 connection.onmessage = e => { console.log(e.data) } 

▍ ImplĂ©mentation du serveur WebSocket dans l'environnement Node.js


Afin d'implémenter un serveur WebSocket dans l'environnement Node.js, vous pouvez utiliser la bibliothÚque ws populaire. Nous l'utilisons pour le développement de serveurs, mais il convient à la création de clients, ainsi qu'à l'organisation de l'interaction entre deux serveurs.

Installez cette bibliothĂšque en initialisant d'abord le projet:

 yarn init yarn add ws 

Le code du serveur WebSocket que nous devons écrire est assez compact:

 constWebSocket = require('ws') const wss = newWebSocket.Server({ port: 8080 }) wss.on('connection', ws => { ws.on('message', message => {   console.log(`Received message => ${message}`) }) ws.send('ho!') }) 

Ici, nous créons un nouveau serveur qui écoute sur le port standard 8080 pour le protocole WebSocket et décrivons un rappel qui, lorsque la connexion est établie, envoie un message ho! au client ho! et imprime sur la console un message reçu du client.

Voici un exemple de travail d'un serveur WebSocket, et voici un client qui peut interagir avec lui.

Résumé


Aujourd'hui, nous avons parlé des mécanismes de mise en réseau pris en charge par la plateforme Node.js, établissant des parallÚles avec des mécanismes similaires utilisés dans les navigateurs. Notre prochain sujet portera sur les fichiers.

Chers lecteurs! Utilisez-vous le protocole WebSocket dans vos applications Web, dont le cÎté serveur a été créé à l'aide de Node.js?

Source: https://habr.com/ru/post/fr424557/


All Articles