Node.js-Handbuch, Teil 8: HTTP- und WebSocket-Protokolle

Node.js ist eine Serverplattform. Die Hauptaufgabe des Servers besteht darin, Anforderungen von Clients, insbesondere von Browsern, so schnell und effizient wie möglich zu verarbeiten. Der achte Teil der Übersetzung des Node.js-Tutorials, das wir heute veröffentlichen, befasst sich mit HTTP und WebSocket.




Was passiert bei HTTP-Anfragen?


Lassen Sie uns darüber sprechen, wie Browser mithilfe des HTTP / 1.1-Protokolls Anforderungen an Server stellen.

Wenn Sie jemals ein Interview im IT-Bereich geführt haben, werden Sie möglicherweise gefragt, was passiert, wenn Sie etwas in die Adressleiste Ihres Browsers eingeben und die Eingabetaste drücken. Vielleicht ist dies eine der beliebtesten Fragen, die bei solchen Interviews auftreten. Jeder, der solche Fragen stellt, möchte wissen, ob Sie einige ziemlich einfache Konzepte erklären und herausfinden können, ob Sie die Prinzipien des Internets verstehen.

Diese Frage berührt viele Technologien, um die allgemeinen Prinzipien zu verstehen, die bedeuten, zu verstehen, wie eines der komplexesten Systeme gebaut wird, die jemals von der Menschheit gebaut wurden und das die ganze Welt abdeckt.

▍ HTTP-Protokoll


Moderne Browser können echte URLs, die in ihre Adressleiste eingegeben wurden, von Suchanfragen unterscheiden, für deren Verarbeitung normalerweise die Standardsuchmaschine verwendet wird. Wir werden über URLs sprechen. Wenn Sie eine Website-Adresse wie flaviocopes.com in die flaviocopes.com , konvertiert der Browser diese Adresse in die Form http://flaviocopes.com , basierend auf der Annahme, dass das HTTP-Protokoll zum Datenaustausch mit der angegebenen Ressource verwendet wird. Bitte beachten Sie, dass unter Windows das, worüber wir hier sprechen, möglicherweise etwas anders aussieht als unter MacOS und Linux.

▍ DNS-Suchphase


Der Browser beginnt mit dem Herunterladen von Daten von der von den Benutzern angeforderten Adresse und führt die DNS-Suchoperation (DNS-Suche) aus, um die IP-Adresse des entsprechenden Servers zu ermitteln. Die symbolischen Namen der in die Adressleiste eingegebenen Ressourcen sind für Personen praktisch. Das Internetgerät bietet jedoch die Möglichkeit, Daten zwischen Computern mithilfe von IP-Adressen auszutauschen, bei denen es sich um Nummernsätze wie 222.324.3.1 (für IPv4) handelt.

Um die IP-Adresse des Servers herauszufinden, überprüft der Browser zunächst den lokalen DNS-Cache, um festzustellen, ob kürzlich ein ähnliches Verfahren durchgeführt wurde. Im Chrome-Browser gibt es beispielsweise eine bequeme Möglichkeit, den DNS-Cache anzuzeigen, indem Sie die folgende Adresse in die Adressleiste eingeben: chrome://net-internals/#dns .

Wenn im Cache nichts gefunden werden kann, ermittelt der Browser mithilfe des POSIX- gethostbyname die IP-Adresse des Servers.

▍ Funktion gethostbyname


Die Funktion gethostbyname überprüft zunächst die hosts , die sich unter macOS oder Linux unter /etc/hosts , um herauszufinden, ob lokale Informationen durch Ermitteln der Serveradresse ermittelt werden können.

Wenn local Mittel zum Auflösen der Anforderung zum Ermitteln der IP-Adresse des Servers fehlschlägt, führt das System eine Anforderung an den DNS-Server durch. Die Adressen solcher Server werden in den Systemeinstellungen gespeichert.

Hier sind einige beliebte DNS-Server:

  • 8.8.8.8: Google DNS-Server.
  • 1.1.1.1: CloudFlare-DNS-Server.

Die meisten Benutzer verwenden die von ihren Anbietern bereitgestellten DNS-Server. Der Browser führt DNS-Abfragen mit dem UDP-Protokoll durch.

TCP und UDP sind zwei grundlegende Protokolle, die in Computernetzwerken verwendet werden. Sie befinden sich auf derselben konzeptionellen Ebene, aber TCP ist ein verbindungsorientiertes Protokoll. Für den Austausch von UDP-Nachrichten, deren Verarbeitung das System geringfügig zusätzlich belastet, ist kein Verbindungsaufbau erforderlich. Wir werden nicht genau darüber sprechen, wie Daten über UDP ausgetauscht werden.

Die IP-Adresse, die dem für uns interessanten Domainnamen entspricht, befindet sich möglicherweise im Cache des DNS-Servers. Ist dies nicht der Fall, wird er den DNS-Stammserver kontaktieren. Das Root-DNS-Serversystem besteht aus 13 Servern, von denen der Betrieb des gesamten Internets abhängt.

Es ist zu beachten, dass der DNS-Stammserver die Korrespondenz zwischen allen vorhandenen Domänennamen und IP-Adressen in der Welt nicht kennt. Ähnliche Server kennen jedoch die Adressen von DNS-Servern der obersten Ebene für Domänen wie .com, .it, .pizza usw.

Nach Erhalt der Anforderung leitet der Root-DNS-Server diese an den DNS-Server der Top-Level-Domain weiter, an den sogenannten TLD-Server (von der Top-Level-Domain).

Angenommen, der Browser sucht nach der IP-Adresse für den flaviocopes.com Server. Wenn Sie sich an den DNS-Stammserver wenden, erhält der Browser von ihm die TLD-Serveradresse für die .com-Zone. Jetzt wird diese Adresse im Cache gespeichert. Wenn Sie also die IP-Adresse einer anderen URL aus der .com-Zone herausfinden müssen, müssen Sie den DNS-Stammserver nicht erneut kontaktieren.

TLD-Server haben IP-Adressen von Nameservern (Name Server, NS), mit deren Hilfe Sie die IP-Adresse anhand der uns vorliegenden URL ermitteln können. Woher erhält der NS-Server diese Informationen? Tatsache ist, dass der Domain-Registrar beim Kauf einer Domain Daten darüber an die Nameserver sendet. Ein ähnliches Verfahren wird beispielsweise beim Ändern des Hostings durchgeführt.

Die betreffenden Server gehören normalerweise Hosting-Anbietern. Zum Schutz vor Ausfällen werden in der Regel mehrere solcher Server erstellt. Beispielsweise können sie folgende Adressen haben:

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

Um die IP-Adresse anhand der URL herauszufinden, wenden sie sich am Ende an solche Server. Sie speichern die tatsächlichen Daten zu IP-Adressen.

Nachdem wir nun die IP-Adresse hinter der in der Adressleiste des Browsers eingegebenen URL ermittelt haben, fahren wir mit dem nächsten Schritt unserer Arbeit fort.

▍ Herstellen einer TCP-Verbindung


Nachdem der Client die IP-Adresse des Servers gelernt hat, kann er eine TCP-Verbindung zu ihm herstellen. Beim Aufbau einer TCP-Verbindung übertragen Client und Server einige Servicedaten aneinander, wonach sie Informationen austauschen können. Dies bedeutet, dass der Client nach dem Herstellen der Verbindung eine Anforderung an den Server senden kann.

▍Anforderung senden


Eine Anfrage ist ein Textfragment, das gemäß den Regeln des verwendeten Protokolls strukturiert ist. Es besteht aus drei Teilen:

  • Abfragezeichenfolge
  • Header anfordern.
  • Text anfordern.

Abfragezeichenfolge


Die Abfragezeichenfolge ist eine einzelne Textzeichenfolge, die die folgenden Informationen enthält:

  • HTTP-Methode.
  • Ressourcenadresse
  • Protokollversion.

Es könnte zum Beispiel so aussehen:

 GET / HTTP/1.1 

Header anfordern


Der Anforderungsheader wird durch eine Reihe von : . Es sind 2 Headerfelder erforderlich, von denen eines Host und das zweite Connection . Die restlichen Felder sind optional.

Der Titel könnte folgendermaßen aussehen:

 Host: flaviocopes.com Connection: close 

Das Feld Host gibt den Domainnamen an, an dem der Browser interessiert ist. Das auf close gesetzte Feld Verbindung bedeutet, dass die Verbindung zwischen Client und Server nicht offen gehalten werden muss.

Andere häufig verwendete Anforderungsheader umfassen Folgendes:

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

In der Tat gibt es noch viel mehr.

Der Anforderungsheader endet mit einer leeren Zeichenfolge.

Text anfordern


Der Anforderungshauptteil ist optional und wird in GET-Anforderungen nicht verwendet. Der Anforderungshauptteil wird sowohl in POST-Anforderungen als auch in anderen Anforderungen verwendet. Es kann beispielsweise Daten im JSON-Format enthalten.

Da es sich jetzt um eine GET-Anfrage handelt, ist der Anfragetext leer und wir werden nicht damit arbeiten.

▍ Antwort


Nachdem der Server die vom Client gesendete Anforderung empfangen hat, verarbeitet er sie und sendet eine Antwort an den Client.

Die Antwort beginnt mit einem Statuscode und einer entsprechenden Nachricht. Wenn die Anforderung erfolgreich ist, sieht der Beginn der Antwort folgendermaßen aus:

 200 OK 

Wenn etwas schief gelaufen ist, gibt es möglicherweise andere Codes. Zum Beispiel Folgendes:

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

Ferner enthält die Antwort eine Liste von HTTP-Headern und den Hauptteil der Antwort (die, da die Anforderung vom Browser ausgeführt wird, HTML-Code ist).

HTML-Analyse


Nachdem der Browser die Antwort des Servers erhalten hat, dessen Hauptteil HTML-Code enthält, beginnt er mit dem Parsen und wiederholt den obigen Vorgang für jede Ressource, die zum Erstellen der Seite benötigt wird. Zu diesen Ressourcen gehören beispielsweise die folgenden:

  • CSS-Dateien.
  • Bilder
  • Webseiten-Symbol (Favicon).
  • JavaScript-Dateien.

Wie genau der Browser die Seite anzeigt, gilt nicht für unsere Konversation. Das Wichtigste, was uns hier interessiert, ist, dass der oben beschriebene Prozess zum Anfordern und Empfangen von Daten nicht nur für HTML-Code verwendet wird, sondern auch für alle anderen Objekte, die mithilfe des HTTP-Protokolls vom Server an den Browser übertragen werden.

Informationen zum Erstellen eines einfachen Servers mit Node.js.


Nachdem wir den Prozess der Interaktion zwischen dem Browser und dem Server untersucht haben, können Sie einen neuen Blick auf den Anwendungsabschnitt First Node.js aus dem ersten Teil dieser Materialreihe werfen, in dem wir den Code für einen einfachen Server beschrieben haben.

HTTP-Anfragen mit Node.js stellen


Um HTTP-Anforderungen mit Node.js auszuführen, wird das entsprechende Modul verwendet . In den folgenden Beispielen wird das https- Modul verwendet. Tatsache ist, dass unter modernen Bedingungen, wann immer möglich, das HTTPS-Protokoll verwendet werden muss.

▍ Ausführen von GET-Anforderungen


Hier ist ein Beispiel für die Ausführung einer GET-Anforderung mit 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() 

▍POST-Anforderungsausführung


So stellen Sie eine POST-Anfrage von 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() 

▍PUT- und DELETE-Abfragen


Die Ausführung solcher Anforderungen sieht genauso aus wie die Ausführung von POST-Anforderungen. Der Hauptunterschied ist neben dem semantischen Inhalt solcher Operationen der Wert der method Eigenschaft des options .

▍ Ausführen von HTTP-Anforderungen in Node.js mithilfe der Axios-Bibliothek


Axios ist eine sehr beliebte JavaScript-Bibliothek, die sowohl im Browser (dies schließt alle modernen Browser und im Internet Explorer ab IE8 ein) als auch in der Node.js-Umgebung funktioniert, mit der HTTP-Anforderungen ausgeführt werden können.

Diese Bibliothek basiert auf Versprechungen und hat einige Vorteile gegenüber Standardmechanismen, insbesondere gegenüber API Fetch. Zu seinen Vorteilen gehören:

  • Unterstützung für ältere Browser (Sie benötigen eine Polyfüllung, um Fetch verwenden zu können).
  • Möglichkeit, Anfragen zu unterbrechen.
  • Unterstützung für das Festlegen von Zeitüberschreitungen für Anforderungen.
  • Eingebauter Schutz gegen CSRF-Angriffe.
  • Unterstützung beim Hochladen von Daten durch Bereitstellung von Informationen zum Fortschritt dieses Prozesses.
  • Unterstützung für die JSON-Datenkonvertierung.
  • Jobs bei Node.js.

Installation


Sie können Axios mit npm installieren:

 npm install axios 

Der gleiche Effekt kann mit Garn erzielt werden:

 yarn add axios 

Sie können die Bibliothek über unpkg.com mit der Seite unpkg.com :

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

Axios API


Sie können eine HTTP-Anfrage mit dem axios Objekt stellen:

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

In der Regel ist es jedoch bequemer, spezielle Methoden anzuwenden:

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

Dies ähnelt der Verwendung von $.get() und $.post() jQuery anstelle von $.ajax() $.post() .

Axios bietet separate Methoden zum Ausführen anderer Arten von HTTP-Anforderungen, die nicht so beliebt sind wie GET und POST, aber dennoch verwendet werden:

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

Die Bibliothek verfügt über eine Methode zum Ausführen einer Anforderung, die nur HTTP-Header ohne Antworttext empfangen soll:

  • axios.head()

GET-Anfragen


Axios ist bequem mit der modernen Async / Wait-Syntax zu verwenden. Das folgende Codebeispiel, das für Node.js entwickelt wurde, verwendet die Bibliothek, um eine Liste von Hunderassen aus der Hunde-API zu laden. Hier wird die Methode axios.get() angewendet und die Steine ​​gezählt:

 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() 

Dasselbe kann ohne Verwendung von async / await umgeschrieben werden, wobei Versprechen angewendet werden:

 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() 

Verwenden von Parametern in GET-Anforderungen


Eine GET-Anforderung kann Parameter enthalten, die in einer URL folgendermaßen aussehen:

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

Bei Verwendung von Axios kann eine solche Abfrage folgendermaßen durchgeführt werden:

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

Der gleiche Effekt kann erzielt werden, indem die Eigenschaft params in einem Objekt mit folgenden Parametern festgelegt wird:

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

POST-Anfragen


Das Ausführen von POST-Anforderungen ist dem Ausführen von GET-Anforderungen sehr ähnlich, aber hier wird anstelle der axios.get() -Methode die axios.post() -Methode verwendet:

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

Als zweites Argument akzeptiert die post Methode ein Objekt mit Anforderungsparametern:

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

Verwenden des WebSocket-Protokolls in Node.js.


WebSocket ist eine Alternative zu HTTP und kann zum Organisieren des Datenaustauschs in Webanwendungen verwendet werden. Mit diesem Protokoll können Sie langlebige bidirektionale Kommunikationskanäle zwischen Client und Server erstellen. Nach dem Herstellen der Verbindung bleibt der Kommunikationskanal offen, wodurch die Anwendung über eine sehr schnelle Verbindung verfügt, die durch geringe Latenzen und eine geringe zusätzliche Belastung des Systems gekennzeichnet ist.

Das WebSocket-Protokoll wird von allen modernen Browsern unterstützt.

▍ HTTP-Unterschiede


HTTP und WebSocket sind sehr unterschiedliche Protokolle, die unterschiedliche Ansätze für den Datenaustausch verwenden. HTTP basiert auf dem "Request-Response" -Modell: Der Server sendet einige Daten an den Client, nachdem er angefordert wurde. Bei WebSocket ist alles anders angeordnet. Nämlich:

  • Der Server kann von sich aus Nachrichten an den Client senden, ohne auf eine Anfrage des Clients warten zu müssen.
  • Der Client und der Server können gleichzeitig Daten austauschen.
  • Bei der Übertragung einer Nachricht wird eine extrem kleine Menge von Servicedaten verwendet. Dies führt insbesondere zu einer geringen Latenz bei der Datenübertragung.

Das WebSocket-Protokoll eignet sich sehr gut für die Echtzeitkommunikation über Kanäle, die lange offen bleiben. HTTP eignet sich wiederum hervorragend für die Organisation gelegentlicher Kommunikationssitzungen, die vom Client initiiert werden. Gleichzeitig ist zu beachten, dass es aus programmtechnischer Sicht viel einfacher ist, den Datenaustausch über das HTTP-Protokoll zu implementieren als über das WebSocket-Protokoll.

▍ Geschützte Version des WebSocket-Protokolls


Es gibt eine unsichere Version des WebSocket-Protokolls ( ws:// URI-Schema), die in Bezug auf die Sicherheit dem http:// -Protokoll ähnelt. Die Verwendung von ws:// sollte vermieden werden, wobei eine sichere Version des Protokolls bevorzugt wird - wss:// .

▍Erstellen einer WebSocket-Verbindung


Um eine WebSocket-Verbindung herzustellen, müssen Sie den entsprechenden Konstruktor verwenden :

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

Nachdem eine erfolgreiche Verbindung hergestellt wurde, wird das open Ereignis ausgelöst. Sie können dieses Ereignis organisieren, indem onopen Eigenschaft onopen des connection eine Rückruffunktion onopen :

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

Zur Behandlung von Fehlern wird der onerror Ereignishandler verwendet:

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

▍Senden von Daten an den Server


Nach dem Öffnen einer WebSocket-Verbindung zum Server können Sie Daten an diesen senden. Dies kann beispielsweise im Onopen- onopen :

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

▍ Abrufen von Daten vom Server


Um Daten zu empfangen, die mit dem WebSocket-Protokoll vom Server gesendet wurden, können Sie den onmessage onmessage zuweisen, der beim Empfang des message aufgerufen wird:

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

▍ Implementierung des WebSocket-Servers in der Node.js-Umgebung


Um einen WebSocket-Server in der Node.js-Umgebung zu implementieren, können Sie die beliebte ws- Bibliothek verwenden. Wir werden es für die Serverentwicklung verwenden, aber es eignet sich zum Erstellen von Clients sowie zum Organisieren der Interaktion zwischen zwei Servern.

Installieren Sie diese Bibliothek, indem Sie zuerst das Projekt initialisieren:

 yarn init yarn add ws 

Der Code für den WebSocket-Server, den wir schreiben müssen, ist ziemlich kompakt:

 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!') }) 

Hier erstellen wir einen neuen Server, der den Standardport 8080 für das WebSocket-Protokoll überwacht und einen Rückruf beschreibt, der beim Herstellen der Verbindung eine ho! Nachricht an den Client sendet ho! und druckt eine vom Client empfangene Nachricht an die Konsole.

Hier ist ein funktionierendes Beispiel für einen WebSocket-Server, und hier ist ein Client, der mit ihm interagieren kann.

Zusammenfassung


Heute haben wir über die von der Node.js-Plattform unterstützten Netzwerkmechanismen gesprochen und Parallelen zu ähnlichen Mechanismen gezogen, die in Browsern verwendet werden. Unser nächstes Thema wird die Arbeit mit Dateien sein.

Liebe Leser! Verwenden Sie das WebSocket-Protokoll in Ihren Webanwendungen, deren Serverseite mit Node.js erstellt wurde?

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


All Articles