Kapitel 4. Netzwerk-Anforderungen

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Einführung

Es ist schwer, heute eine Webanwendung zu finden, die keine Netzwerkanfragen sendet. Seit den Anfängen des Web 2.0 und dem neuartigen Ansatz, der als Ajax (Asynchronous JavaScript and XML) bekannt ist, senden Webanwendungen asynchrone Anfragen, um neue Daten zu erhalten, ohne die gesamte Seite neu zu laden. Mit der XMLHttpRequest API begann eine neue Ära interaktiver JavaScript-Anwendungen. Trotz des Namens kann XMLHttpRequest (oder XHR, wie es manchmal genannt wird) auch mit JSON und Formulardaten arbeiten.

XMLHttpRequest war ein Durchbruch, aber die API kann mühsam zu bedienen sein. Schließlich fügten Bibliotheken von Drittanbietern wie Axios und jQuery schlankere APIs hinzu, die die zentrale XHR-API umhüllten.

Im Jahr 2015 wurde eine neuere, auf Promise basierende API namens Fetch zum neuen Standard, und die Browser begannen nach und nach, sie zu unterstützen. Heute ist Fetch die Standardmethode, um asynchrone Anfragen von deinen Webanwendungen zu stellen.

Dieses Kapitel befasst sich mit XHR und Fetch sowie mit einigen anderen APIs für dieNetzwerkkommunikation:

Baken

Eine einfache Einweg-POST-Anfrage, ideal zum Senden von Analysedaten

Vom Server gesendete Ereignisse

Eine einseitige, dauerhafte Verbindung mit einem Server zum Empfang von Echtzeit-Ereignissen

WebSockets

Eine dauerhafte Zwei-Wege-Verbindung für bidirektionale Kommunikation

Senden einer Anfrage mit XMLHttpRequest

Problem

Du möchtest eine GET-Anfrage an eine öffentliche API senden und ältere Browser unterstützen, die die Fetch-API nicht implementieren.

Lösung

Verwende die XMLHttpRequest-API. XMLHttpRequest ist eine asynchrone, ereignisbasierte API für Netzwerkanfragen. Die allgemeine Verwendung von XMLHttpRequest ist folgende:

  1. Erstelle ein neues XMLHttpRequest Objekt.

  2. Füge einen Listener für das Ereignis load hinzu, der die Antwortdaten empfängt.

  3. Rufe open für die Anfrage auf und übergebe die HTTP-Methode und die URL.

  4. Zum Schluss rufst du send für die Anfrage auf. Dies löst das Senden der HTTP-Anfrage aus.

Beispiel 4-1 zeigt ein einfaches Beispiel für die Arbeit mit JSON-Daten mithilfe einer XHR.

Beispiel 4-1. Eine GET-Anfrage mit XMLHttpRequest stellen
/**
 * Loads user data from the URL /api/users, then prints them
 * to the console
 */
function getUsers() {
  const request = new XMLHttpRequest();

  request.addEventListener('load', event => {
    // The event target is the XHR itself; it contains a
    // responseText property that we can use to create a JavaScript object from
    // the JSON text.
    const users = JSON.parse(event.target.responseText);
    console.log('Got users:', users);
  });

  // Handle any potential errors with the request.
  // This only handles network errors. If the request
  // returns an error status like 404, the 'load' event still fires
  // where you can inspect the status code.
  request.addEventListener('error', err => {
    console.log('Error!', err);
  });

  request.open('GET', '/api/users');
  request.send();
}

Diskussion

Die XMLHttpRequest API ist eine ereignisbasierte API. Wenn die Antwort empfangen wird, wird ein load Ereignis ausgelöst. In Beispiel 4-1 übergibt derload Event Handler den rohen Antworttext an JSON.parse. Er erwartet, dass der Antwortkörper JSON ist und verwendet JSON.parse, um den JSON-String in ein Objekt zu verwandeln.

Wenn beim Laden der Daten ein Fehler auftritt, wird das Ereignis error ausgelöst. Damit werden Verbindungs- oder Netzwerkfehler behandelt, aber ein HTTP-Statuscode, der als "Fehler" gilt, wie 404 oder 500, löst dieses Ereignis nicht aus. Stattdessen löst es auch das Ereignis load aus.

Um dich vor solchen Fehlern zu schützen, musst du die Eigenschaft status der Antwort untersuchen, um festzustellen, ob eine solche Fehlersituation vorliegt. Du kannst darauf zugreifen, indem du event.target.status referenzierst.

Fetch wird schon seit langem unterstützt. Wenn du also keine wirklich alten Browser unterstützen musst, wirst du höchstwahrscheinlich XMLHttpRequest nicht verwenden müssen. Die meiste Zeit - wenn nicht sogar die ganze Zeit - wirst du die Fetch-API verwenden.

Senden einer GET-Anfrage mit der Fetch-API

Problem

Du möchtest mit einem modernen Browser eine GET-Anfrage an eine öffentliche API senden.

Lösung

Verwende die Fetch-API. Fetch ist eine neuere Anfrage-API, die Promises verwendet. Sie ist sehr flexibel und kann alle Arten von Daten senden, aber Beispiel 4-2 sendet eine einfache GET-Anfrage an eine API.

Beispiel 4-2. Senden einer GET-Anfrage mit der Fetch-API
/**
 * Loads users by calling the /api/users API, and parses the
 * response JSON.
 * @returns a Promise that resolves to an array of users returned by the API
 */
function loadUsers() {
  // Make the request.
  return fetch('/api/users')
    // Parse the response body to an object.
    .then(response => response.json())
    // Handle errors, including network and JSON parsing errors.
    .catch(error => console.error('Unable to fetch:', error.message));
}

loadUsers().then(users => {
  console.log('Got users:', users);
});

Diskussion

Die Fetch-API ist übersichtlicher. Sie gibt eine Promise zurück, die in ein Objekt aufgelöst wird, das die HTTP-Antwort darstellt. Das response Objekt enthält Daten wie den Statuscode, die Kopfzeilen und den Body.

Um den JSON-Antwortkörper zu erhalten, musst du die Methode json der Antwort aufrufen. Diese Methode liest den Body aus dem Stream und gibt eine Promise zurück, die den als Objekt geparsten JSON-Body auflöst. Wenn der Antwortkörper kein gültiges JSON ist, wird die Promise zurückgewiesen.

Die Antwort hat auch Methoden, um den Textkörper in anderen Formaten zu lesen, wie z.B. FormData oder einen einfachen Textstring.

Da Fetch mit Promises funktioniert, kannst du auch await verwenden, wie in Beispiel 4-3 gezeigt.

Beispiel 4-3. Fetch mit async verwenden /await
async function loadUsers() {
  try {
    const response = await fetch('/api/users');
    return response.json();
  } catch (error) {
    console.error('Error loading users:', error);
  }
}

async function printUsers() {
  const users = await loadUsers();
  console.log('Got users:', users);
}
Hinweis

Erinnere dich daran, dass vor der Verwendung von await in einer Funktion, diese Funktion das Schlüsselwort async haben muss.

Senden einer POST-Anfrage mit der Fetch-API

Problem

Du möchtest eine POST-Anfrage an eine API senden, die einen JSON-Anfragetext erwartet.

Lösung

Verwende die Fetch-API und gib die Methode (POST), den JSON-Body und den Inhaltstyp an (siehe Beispiel 4-4).

Beispiel 4-4. Senden von JSON-Nutzdaten per POST mit der Fetch-API
/**
 * Creates a new user by sending a POST request to /api/users.
 * @param firstName The user's first name
 * @param lastName The user's last name
 * @param department The user's department
 * @returns a Promise that resolves to the API response body
 */
function createUser(firstName, lastName, department) {
  return fetch('/api/users', {
    method: 'POST',
    body: JSON.stringify({ firstName, lastName, department }),
    headers: {
      'Content-Type': 'application/json'
    }
  })
    .then(response => response.json());
}

createUser('John', 'Doe', 'Engineering')
  .then(() => console.log('Created user!'))
  .catch(error => console.error('Error creating user:', error));

Diskussion

Beispiel 4-4 sendet einige JSON-Daten in einer POST Anfrage. Wenn du JSON.stringify für das Benutzerobjekt aufrufst, wird es in einen JSON-String umgewandelt, der benötigt wird, um ihn als Body mit fetch zu senden. Du musst auch den Content-Type Header setzen, damit der Server weiß, wie er den Body interpretieren soll.

Mit Fetch kannst du auch andere Inhaltstypen als Body senden. Beispiel 4-5 zeigt, wie du eine POST Anfrage mit einigen Formulardaten senden würdest.

Beispiel 4-5. Senden von Formulardaten in einer POST-Anfrage
fetch('/login', {
  method: 'POST',
  body: 'username=sysadmin&password=password',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
  }
})
  .then(response => response.json())
  .then(data => console.log('Logged in!', data))
  .catch(error => console.error('Request failed:', error));

Hochladen einer Datei mit der Fetch API

Problem

Du möchtest Dateidaten mit einer POST-Anfrage hochladen, indem du die Fetch-API verwendest.

Lösung

Verwende ein <input type="file"> Element und sende den Dateiinhalt als Request Body (siehe Beispiel 4-6).

Beispiel 4-6. Senden von Dateidaten mit der Fetch API
/**
 * Given a form with a 'file' input, sends a POST request containing
 * the file data in its body.
 * @param form the form object (should have a file input with the name 'file')
 * @returns a Promise that resolves when the response JSON is received
 */
function uploadFile(form) {
  const formData = new FormData(form);
  const fileData = formData.get('file');
  return fetch('https://httpbin.org/post', {
    method: 'POST',
    body: fileData
  })
    .then(response => response.json());
}

Diskussion

Um eine Datei mit modernen Browser-APIs hochzuladen, sind nicht viele Schritte nötig. Die <input type="file"> stellt die Dateidaten über die FormData-API bereit und wird in den Body der POST-Anfrage aufgenommen. Der Browser kümmert sich um den Rest.

Ein Leuchtfeuer senden

Problem

Du willst eine schnelle Anfrage senden, ohne auf eine Antwort zu warten, zum Beispiel um Analysedaten zu senden.

Lösung

Verwende die Beacon-API, um Daten in einer POST-Anfrage zu senden. Eine normale POST-Anfrage mit der Fetch-API wird möglicherweise nicht rechtzeitig abgeschlossen, bevor die Seite entladen wird. Bei der Verwendung eines Beacons ist die Wahrscheinlichkeit größer, dass sie erfolgreich ist (siehe Beispiel 4-7). Der Browser muss nicht auf eine Antwort warten, und die Anfrage wird eher erfolgreich sein, wenn sie gesendet wird, während der Nutzer deine Seite verlässt.

Beispiel 4-7. Senden einer Bake
const currentUser = {
  username: 'sysadmin'
};

// Some analytics data we want to capture
const data = {
  user: currentUser.username,
  lastVisited: new Date()
};

// Send the data before unload.
document.addEventListener('visibilitychange', () => {
  // If the visibility state is 'hidden', that means the page just became hidden.
  if (document.visibilityState === 'hidden') {
    navigator.sendBeacon('/api/analytics', data);
  }
});

Diskussion

Bei einem Aufruf von XMLHttpRequest oder fetch wartet der Browser auf die Antwort und gibt sie zurück (mit einem Ereignis oder Promise). Im Allgemeinen musst du bei einseitigen Anfragen, wie dem Senden von Analysedaten, nicht auf die Antwort warten.

Anstelle von Promise gibt navigator.sendBeacon einen booleschen Wert zurück, der angibt, ob der Sendevorgang geplant wurde. Es gibt keine weiteren Ereignisse oder Benachrichtigungen.

navigator.sendBeacon sendet immer eine POST Anfrage. Wenn du mehrere Analysedatensätze senden willst, z. B. eine Sammlung von UI-Interaktionen, kannst du sie in einem Array sammeln, während der Nutzer mit deiner Seite interagiert, und das Array dann als POST Body mit dem Beacon senden.

Lauschen auf entfernte Ereignisse mit vom Server gesendeten Ereignissen

Problem

Du möchtest Benachrichtigungen von deinem Backend-Server ohne wiederholtes Polling erhalten.

Lösung

Verwende die EventSource API, um vom Server gesendete Ereignisse (SSE) zu empfangen.

Um das Abhören von SSE zu starten, erstellst du eine neue Instanz von EventSource und gibst die URL als erstes Argument an (siehe Beispiel 4-8).

Beispiel 4-8. Öffnen einer SSE-Verbindung
const events = new EventSource('https://example.com/events');

// Fired once connected
events.addEventListener('open', () => {
  console.log('Connection is open');
});

// Fired if a connection error occurs
events.addEventListener('error', event => {
  console.log('An error occurred:', event);
});

// Fired when receiving an event with a type of 'heartbeat'
events.addEventListener('heartbeat', event => {
  console.log('got heartbeat:', event.data);
});

// Fired when receiving an event with a type of 'notice'
events.addEventListener('notice', event => {
  console.log('got notice:', event.data);
})

// The EventSource leaves the connection open. If we want to close the connection,
// we need to call close on the EventSource object.
function cleanup() {
  events.close();
}

Diskussion

Ein EventSource muss sich mit einem speziellen HTTP-Endpunkt verbinden, der die Verbindung mit einem Content-Type Header von text/event-stream offen lässt. Immer wenn ein Ereignis eintritt, kann der Server eine neue Nachricht über die offene Verbindung senden.

Hinweis

Wie MDN berichtet, ist es sehr empfehlenswert, HTTP/2 mit SSE zu verwenden. Andernfalls schränken die Browser die Anzahl der EventSource Verbindungen pro Domain stark ein. In diesem Fall können es nur bis zu sechs Verbindungen sein.

Dieses Limit gilt nicht pro Tab, sondern für alle Tabs des Browsers auf einer bestimmten Domain.

Wenn EventSource ein Ereignis über eine dauerhafte Verbindung empfängt, ist es reiner Text. Du kannst den Text des Ereignisses über die Eigenschaft data des empfangenen Ereignisobjekts abrufen. Hier ist ein Beispiel für ein Ereignis des Typs notice:

event: notice
data: Connection established at 10:51 PM, 2023-04-22
id: 3

Um auf dieses Ereignis zu warten, rufe addEventListener('notice') für das EventSource Objekt auf. Das Ereignisobjekt hat eine Eigenschaft data, deren Wert der String-Wert ist, der im Ereignis mit data: vorangestellt ist.

Wenn ein Ereignis keinen Ereignistyp hat, kannst du auf das generische Ereignis message hören, um es zu empfangen .

Daten in Echtzeit mit WebSockets austauschen

Problem

Du willst Daten in Echtzeit senden und empfangen, ohne den Server immer wieder mit Fetch-Anfragen abfragen zu müssen.

Lösung

Verwende die WebSocket-API, um eine dauerhafte Verbindung zu deinem Backend-Server herzustellen (siehe Beispiel 4-9).

Beispiel 4-9. Erstellen einer WebSocket-Verbindung
// Open the WebSocket connection (the URL scheme should be ws: or wss:).
const socket = new WebSocket(url);

socket.addEventListener('open', onSocketOpened);
socket.addEventListener('message', handleMessage);
socket.addEventListener('error', handleError);
socket.addEventListener('close', onSocketClosed);

function onSocketOpened() {
  console.log('Socket ready for messages');
}

function handleMessage(event) {
  console.log('Received message:', event.data);
}

function handleError(event) {
  console.log('Socket error:', event);
}

function onSocketClosed() {
  console.log('Connection was closed');
}
Hinweis

Um WebSockets zu nutzen, muss dein Server einen WebSocket-fähigen Endpunkt haben, mit dem du dich verbinden kannst. Auf MDN findest du eine ausführliche Anleitung zum Erstellen eines WebSocket-Servers.

Sobald der Socket das Ereignis open auslöst, kannst du mit dem Senden von Nachrichten beginnen, wie in Beispiel 4-10 gezeigt.

Beispiel 4-10. Senden von WebSocket-Nachrichten
// Messages are simple strings.
socket.send('Hello');

// The socket needs the data as a string, so you can use
// JSON.stringify to serialize objects to be sent.
socket.send(JSON.stringify({
  username: 'sysadmin',
  password: 'password'
}));

Eine WebSocket-Verbindung ist eine bidirektionale Verbindung. Empfangene Daten vom Server lösen ein message Ereignis aus. Du kannst diese nach Bedarf bearbeiten oder sogar eine Antwort senden (siehe Beispiel 4-11).

Beispiel 4-11. Antwort auf eine WebSocket-Nachricht
socket.addEventListener('message', event => {
  socket.send('ACKNOWLEDGED');
});

Wenn du fertig bist, kannst du die Verbindung schließen, indem du close für das WebSocket-Objekt aufrufst.

Diskussion

WebSockets eignen sich gut für Anwendungen, die Echtzeitfunktionen benötigen, wie z. B. ein Chatsystem oder die Überwachung von Ereignissen. WebSocket-Endpunkte haben ein ws:// oder wss:// Schema. Diese sind analog zu http:// und https://- eines ist unsicher und das andere verwendetVerschlüsselung.

Um eine WebSocket-Verbindung zu initiieren, sendet der Browser zunächst eine GET Anfrage an den WebSocket-Endpunkt. Der Request Payload für die URL wss://example.com/websocket sieht wie folgt aus:

GET /websocket HTTP/1.1
Host: example.com
Sec-WebSocket-Key: aSBjYW4gaGFzIHdzIHBsej8/
Sec-WebSocket-Version: 13
Connection: Upgrade
Upgrade: websocket

Dadurch wird ein WebSocket-Handshake eingeleitet. Wenn er erfolgreich ist, antwortet der Server mit dem Statuscode 101 (Switching Protocols):

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: bm8gcGVla2luZywgcGxlYXNlIQ==

Das WebSocket-Protokoll legt einen Algorithmus fest, um einen Sec-Websocket-Accept Header zu generieren, der auf dem Sec-WebSocket-Key der Anfrage basiert. Der Client verifiziert diesen Wert, und zu diesem Zeitpunkt ist die Zwei-Wege-WebSocket-Verbindung aktiv und der Socket feuert das open Ereignis ab.

Sobald die Verbindung geöffnet ist, kannst du mit dem Ereignis message auf Nachrichten warten und mit dem Aufruf send auf dem Socket-Objekt Nachrichten senden. Später kannst du die WebSocket-Sitzung beenden, indem du close für das Socket-Objekt aufrufst.

Get Web API Kochbuch now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.