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
Lösung
Verwende die XMLHttpRequest-API. XMLHttpRequest ist eine asynchrone, ereignisbasierte API für Netzwerkanfragen. Die allgemeine Verwendung von XMLHttpRequest ist folgende:
-
Erstelle ein neues
XMLHttpRequest
Objekt. -
Füge einen Listener für das Ereignis
load
hinzu, der die Antwortdaten empfängt. -
Rufe
open
für die Anfrage auf und übergebe die HTTP-Methode und die URL. -
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
Lösung
Verwende die Fetch-API. Fetch ist eine neuere Anfrage-API, die Promise
s 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 Promise
s 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
);
}
Senden einer POST-Anfrage mit der Fetch-API
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
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
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
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
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.