Hintergrund
Mit Service Workern haben Webentwickler die Möglichkeit, auf Netzwerkanfragen ihrer Webanwendungen zu reagieren, damit sie auch offline weiterarbeiten können. Außerdem können sie Lie-Fi bekämpfen und komplexe Cache-Interaktionen wie veraltete Neuvalidierung implementieren. Service Worker waren jedoch bisher an einen bestimmten Ursprung gebunden. Als Inhaber einer Webanwendung sind Sie dafür verantwortlich, einen Service Worker zu schreiben und bereitzustellen, der alle Netzwerkanfragen Ihrer Webanwendung abfängt. In diesem Modell ist jeder Service Worker für die Verarbeitung von Anfragen zwischen verschiedenen Ursprüngen verantwortlich, z. B. an eine Drittanbieter-API oder für Web-Schriftarten.
Was wäre, wenn ein Drittanbieter einer API, Webfonts oder eines anderen häufig verwendeten Dienstes seinen eigenen Service Worker bereitstellen könnte, der Anfragen von anderen Ursprüngen an seinen Ursprung weiterleiten könnte? Anbieter können ihre eigene benutzerdefinierte Netzwerklogik implementieren und eine einzelne autoritative Cache-Instanz zum Speichern ihrer Antworten nutzen. Dank foreign fetch ist diese Art der Bereitstellung von Drittanbieter-Dienstworkern jetzt Realität.
Die Bereitstellung eines Service Workers, der Foreign Fetch implementiert, ist für jeden Anbieter eines Dienstes sinnvoll, auf den über HTTPS-Anfragen von Browsern zugegriffen wird. Denken Sie nur an Szenarien, in denen Sie eine netzwerkunabhängige Version Ihres Dienstes bereitstellen könnten, in der Browser einen gemeinsamen Ressourcencache nutzen könnten. Beispiele für Dienste, die davon profitieren könnten:
- API-Anbieter mit RESTful-Schnittstellen
- Anbieter von Webschriften
- Analyseanbieter
- Anbieter für Bildhosting
- Generische Content Delivery Networks
Angenommen, Sie sind ein Analyseanbieter. Wenn Sie einen externen Dienst-Worker für den Abruf bereitstellen, können Sie dafür sorgen, dass alle Anfragen an Ihren Dienst, die fehlschlagen, während ein Nutzer offline ist, in der Warteschlange platziert und wiedergegeben werden, sobald die Verbindung wiederhergestellt ist. Es ist zwar möglich, dass die Clients eines Dienstes ein ähnliches Verhalten über eigene Service Worker implementieren. Es ist jedoch nicht so skalierbar, wenn jeder Client eine spezielle Logik für Ihren Dienst schreiben muss, als die Verwendung eines von Ihnen bereitgestellten externen Service Workers für den Fremdabruf.
Vorbereitung
Ursprungstest-Token
Die Funktion „Externer Abruf“ befindet sich noch in der Testphase. Um zu vermeiden, dass dieses Design vorzeitig implementiert wird, bevor es vollständig von den Browseranbietern spezifiziert und vereinbart wurde, wurde es in Chrome 54 als Origin Trial implementiert. Solange die externe Abruffunktion experimentell ist, musst du ein Token anfordern, das auf den Ursprung deines Dienstes beschränkt ist, um diese neue Funktion mit dem von dir gehosteten Dienst zu verwenden. Das Token sollte als HTTP-Antwortheader in allen ursprungsübergreifenden Anfragen für Ressourcen enthalten sein, die Sie per fremdem Abruf verarbeiten möchten, sowie in der Antwort für Ihre Service Worker-JavaScript-Ressource:
Origin-Trial: token_obtained_from_signup
Der Testzeitraum endet im März 2017. Wir gehen davon aus, dass wir alle notwendigen Änderungen zur Stabilisierung der Funktion gefunden und diese (hoffentlich) standardmäßig aktiviert haben. Wenn die Abfrage externer Daten bis dahin nicht standardmäßig aktiviert ist, funktionieren die Funktionen, die mit vorhandenen Origin Trial-Tokens verknüpft sind, nicht mehr.
Um vor der Registrierung für ein offizielles Ursprungstesttoken das Experimentieren mit ausländischen Abrufen zu erleichtern, können Sie die Anforderung in Chrome für Ihren lokalen Computer umgehen. Rufen Sie dazu chrome://flags/#enable-experimental-web-platform-features
auf und aktivieren Sie das Flag „Experimental Web Platform features“. Hinweis: Diese Schritte müssen in jeder Chrome-Instanz ausgeführt werden, die Sie in Ihren lokalen Tests verwenden möchten. Mit einem Origin Trial-Token ist die Funktion dagegen für alle Ihre Chrome-Nutzer verfügbar.
HTTPS
Wie bei allen Service Worker-Bereitstellungen muss auf den Webserver, auf dem sowohl Ihre Ressourcen als auch Ihr Service Worker-Script bereitgestellt werden, über HTTPS zugegriffen werden. Außerdem gilt die Abfangung von externen Abrufen nur für Anfragen, die von Seiten stammen, die auf sicheren Ursprüngen gehostet werden. Die Clients Ihres Dienstes müssen also HTTPS verwenden, um die Implementierung von externen Abrufen nutzen zu können.
Foreign Fetch verwenden
Wenn die Voraussetzungen erledigt sind, schauen wir uns jetzt die technischen Details an, die erforderlich sind, um einen fremden Abruf-Service-Worker zum Laufen zu bringen.
Service Worker registrieren
Die erste Herausforderung, auf die Sie wahrscheinlich stoßen, ist die Registrierung Ihres Service Workers. Wenn Sie schon einmal mit Service Workern gearbeitet haben, sind Ihnen wahrscheinlich die folgenden Punkte bekannt:
// You can't do this!
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js');
}
Dieser JavaScript-Code für die Registrierung eines eigenen Dienstarbeiters ist im Kontext einer Webanwendung sinnvoll, die ausgelöst wird, wenn ein Nutzer eine von Ihnen verwaltete URL aufruft. Es ist jedoch keine praktikable Lösung, einen Service Worker eines Drittanbieters zu registrieren, wenn der Browser nur eine bestimmte Unterressource und keine vollständige Navigation von Ihrem Server anfordert. Wenn der Browser beispielsweise ein Bild von einem von Ihnen verwalteten CDN-Server anfordert, können Sie dieses JavaScript-Snippet nicht an den Anfang Ihrer Antwort setzen und erwarten, dass es ausgeführt wird. Es ist eine andere Methode zur Registrierung von Dienstarbeitern außerhalb des normalen JavaScript-Ausführungskontexts erforderlich.
Die Lösung besteht aus einem HTTP-Header, den Ihr Server in jede Antwort aufnehmen kann:
Link: </service-worker.js>; rel="serviceworker"; scope="/"
Sehen wir uns diesen Beispielheader einmal genauer an. Die einzelnen Komponenten sind durch das Zeichen ;
voneinander getrennt.
</service-worker.js>
ist erforderlich und wird verwendet, um den Pfad zur Service Worker-Datei anzugeben. Ersetzen Sie/service-worker.js
durch den entsprechenden Pfad zu Ihrem Script. Dies entspricht direkt dem StringscriptURL
, der ansonsten als erster Parameter annavigator.serviceWorker.register()
übergeben würde. Der Wert muss in<>
-Zeichen eingeschlossen sein (wie in derLink
-Header-Spezifikation gefordert). Wenn eine relative statt einer absoluten URL angegeben wird, wird sie als relativ zum Speicherort der Antwort interpretiert.rel="serviceworker"
ist ebenfalls erforderlich und sollte ohne Anpassungen enthalten sein.scope=/
ist eine optionale Bereichsdeklaration, die dem Stringoptions.scope
entspricht, den Sie als zweiten Parameter annavigator.serviceWorker.register()
übergeben können. Für viele Anwendungsfälle ist der Standardumfang ausreichend. Sie können diesen Parameter also weglassen, es sei denn, Sie wissen, dass Sie ihn benötigen. Für die Registrierung vonLink
-Headern gelten dieselben Einschränkungen hinsichtlich des maximal zulässigen Gültigkeitsbereichs sowie die Möglichkeit, diese Einschränkungen über den HeaderService-Worker-Allowed
zu lockern.
Genau wie bei einer „traditionellen“ Service Worker-Registrierung wird mit dem Link
-Header ein Service Worker installiert, der für die nächste Anfrage an den registrierten Umfang verwendet wird. Der Antworttext mit dem speziellen Header wird unverändert verwendet und ist für die Seite sofort verfügbar, ohne dass auf die Fertigstellung der Installation des externen Service Workers gewartet werden muss.
Die externe Abruffunktion ist derzeit als Ursprungstest implementiert. Daher müssen Sie neben dem Link-Antwortheader auch einen gültigen Origin-Trial
-Header angeben. Die Mindestanzahl von Antwortheadern, die zum Registrieren des externen Fetch-Dienst-Workers hinzugefügt werden müssen, ist
Link: </service-worker.js>; rel="serviceworker"
Origin-Trial: token_obtained_from_signup
Registrierung debuggen
Während der Entwicklung sollten Sie prüfen, ob der Worker für den externen Abrufdienst richtig installiert ist und Anfragen verarbeitet. In den Entwicklertools von Chrome können Sie einige Dinge prüfen, um sicherzustellen, dass alles wie erwartet funktioniert.
Werden die richtigen Antwortheader gesendet?
Um den Service Worker für fremde Abrufe zu registrieren, müssen Sie einen Link-Header für eine Antwort auf eine in Ihrer Domain gehostete Ressource festlegen, wie weiter oben in diesem Beitrag beschrieben. Während des Ursprungstests müssen Sie, sofern Sie chrome://flags/#enable-experimental-web-platform-features
nicht festgelegt haben, auch einen Origin-Trial
-Antwortheader festlegen. Ob Ihr Webserver diese Header setzt, können Sie im Bereich „Netzwerk“ der Entwicklertools prüfen:
Ist der Service Worker für den fremden Abruf ordnungsgemäß registriert?
Sie können auch die zugrunde liegende Service Worker-Registrierung einschließlich ihres Umfangs prüfen. Sehen Sie sich dazu die vollständige Liste der Service Worker im Anwendungsbereich der Entwicklertools an. Wählen Sie die Option „Alle anzeigen“ aus, da standardmäßig nur Dienstprogramme für den aktuellen Ursprung angezeigt werden.
Der Installations-Event-Handler
Nachdem Sie den Service Worker des Drittanbieters registriert haben, kann er wie jeder andere Service Worker auf die Ereignisse install
und activate
reagieren. Diese Ereignisse können beispielsweise genutzt werden, um während des Ereignisses install
Caches mit erforderlichen Ressourcen zu füllen oder veraltete Caches im Ereignis activate
zu bereinigen.
Zusätzlich zu den normalen install
-Ereignis-Caching-Aktivitäten ist im install
-Ereignishandler des Drittanbieter-Dienstearbeiters ein zusätzlicher Schritt erforderlich. Ihr Code muss registerForeignFetch()
aufrufen, wie im folgenden Beispiel:
self.addEventListener('install', event => {
event.registerForeignFetch({
scopes: [self.registration.scope], // or some sub-scope
origins: ['*'] // or ['https://example.com']
});
});
Es gibt zwei Konfigurationsoptionen, die beide erforderlich sind:
scopes
verwendet ein Array mit einem oder mehreren Strings, von denen jeder einen Bereich für Anfragen darstellt, die einforeignfetch
-Ereignis auslösen. Aber Moment, denken Sie vielleicht, ich habe bereits bei der Registrierung des Service Workers einen Gültigkeitsbereich definiert! Das ist richtig und dieser Gesamtumfang ist weiterhin relevant. Jeder hier angegebene Umfang muss entweder mit dem Gesamtumfang des Service Workers übereinstimmen oder ein untergeordneter Umfang sein. Mit den zusätzlichen Einschränkungen für den Geltungsbereich können Sie einen universellen Dienst-Worker bereitstellen, der sowohlfetch
-Ereignisse von selbst erhobenen Daten (für Anfragen von Ihrer eigenen Website) als auchforeignfetch
-Ereignisse von Drittanbietern (für Anfragen von anderen Domains) verarbeiten kann. Außerdem können Sie festlegen, dassforeignfetch
nur von einem Teil Ihres größeren Geltungsbereichs ausgelöst werden soll. Wenn Sie einen Service Worker bereitstellen, der nur Drittanbieter-foreignfetch
-Ereignisse verarbeiten soll, sollten Sie in der Praxis nur einen einzelnen, expliziten Gültigkeitsbereich verwenden, der dem Gesamtumfang Ihres Service Workers entspricht. Das ist im Beispiel oben der Fall, in dem der Wertself.registration.scope
verwendet wird.origins
akzeptiert auch ein Array mit einem oder mehreren Strings und ermöglicht es dir, denforeignfetch
-Handler so zu beschränken, dass er nur auf Anfragen von bestimmten Domains antwortet. Wenn Sie beispielsweise „https://beispiel.de“ explizit zulassen, wird durch eine Anfrage von einer unterhttps://example.com/path/to/page.html
gehosteten Seite für eine Ressource, die aus Ihrem fremden Abrufbereich bereitgestellt wird, der Handler für fremde Abrufe ausgelöst, Anfragen vonhttps://random-domain.com/path/to/page.html
jedoch nicht den Handler. Sofern Sie keinen bestimmten Grund haben, Ihre Logik für den externen Abruf nur für einen Teil der Remote-Quellen auszulösen, können Sie einfach'*'
als einzigen Wert im Array angeben. In diesem Fall sind alle Quellen zulässig.
Der Event-Handler „foreignfetch“
Nachdem Sie den Service Worker des Drittanbieters installiert und über registerForeignFetch()
konfiguriert haben, kann er Subressourcenanforderungen zwischen verschiedenen Ursprüngen an Ihren Server abfangen, die in den Bereich des externen Abrufs fallen.
In einem herkömmlichen eigenen Service Worker löst jede Anfrage ein fetch
-Ereignis aus, auf das Ihr Service Worker antworten konnte. Unser Service Worker erhält die Möglichkeit, ein etwas anderes Ereignis namens foreignfetch
zu verarbeiten. Konzeptionell sind die beiden Ereignisse sehr ähnlich und bieten die Möglichkeit, die eingehende Anfrage zu prüfen und optional über respondWith()
eine Antwort darauf zu geben:
self.addEventListener('foreignfetch', event => {
// Assume that requestLogic() is a custom function that takes
// a Request and returns a Promise which resolves with a Response.
event.respondWith(
requestLogic(event.request).then(response => {
return {
response: response,
// Omit to origin to return an opaque response.
// With this set, the client will receive a CORS response.
origin: event.origin,
// Omit headers unless you need additional header filtering.
// With this set, only Content-Type will be exposed.
headers: ['Content-Type']
};
})
);
});
Trotz der konzeptionellen Ähnlichkeiten gibt es ein paar Unterschiede in der Praxis, wenn respondWith()
für ein ForeignFetchEvent
aufgerufen wird. Anstatt einfach einen Response
(oder einen Promise
, der in einen Response
aufgelöst wird) für respondWith()
anzugeben, wie du es bei einem FetchEvent
tust, musst du einen Promise
übergeben, der in ein Objekt mit bestimmten Properties für die respondWith()
des ForeignFetchEvent
aufgelöst wird:
response
ist erforderlich und muss auf dasResponse
-Objekt festgelegt werden, das an den Client zurückgegeben wird, der die Anfrage gestellt hat. Wenn du etwas anderes als eine gültigeResponse
angibst, wird die Anfrage des Clients mit einem Netzwerkfehler beendet. Anders als beim Aufrufen vonrespondWith()
in einemfetch
-Event-Handler müssen Sie hier einenResponse
angeben und keinPromise
-Objekt, das mit einemResponse
aufgelöst wird. Sie können Ihre Antwort über eine Promise-Kette erstellen und diese Kette als Parameter anforeignfetch
srespondWith()
übergeben. Die Kette muss jedoch in ein Objekt aufgelöst werden, das die Eigenschaftresponse
enthält, die auf einResponse
-Objekt festgelegt ist. Eine Demonstration dazu finden Sie im Codebeispiel oben.origin
ist optional und wird verwendet, um zu bestimmen, ob die zurückgegebene Antwort undurchsichtig ist. Wenn Sie diese Angabe weglassen, ist die Antwort undurchsichtig und der Client hat eingeschränkten Zugriff auf den Text und die Header der Antwort. Wenn die Anfrage mitmode: 'cors'
gesendet wurde, wird die Rückgabe einer undurchsichtigen Antwort als Fehler behandelt. Wenn Sie jedoch einen Stringwert angeben, der dem Ursprung des Remote-Clients entspricht (der überevent.origin
abgerufen werden kann), aktivieren Sie ausdrücklich die Bereitstellung einer CORS-kompatiblen Antwort für den Client.headers
ist ebenfalls optional und nur nützlich, wenn du auchorigin
angibst und eine CORS-Antwort zurückgibst. Standardmäßig werden nur Header in Ihrer Antwort berücksichtigt, die in der Liste der CORS-gesicherten Antwortheader enthalten sind. Wenn Sie die zurückgegebenen Daten weiter filtern möchten, können Sie eine Liste mit einem oder mehreren Headernamen angeben. Diese Liste wird dann als Zulassungsliste für die Header verwendet, die in der Antwort angezeigt werden sollen. So können Sie CORS aktivieren und gleichzeitig verhindern, dass potenziell vertrauliche Antwortheader direkt dem Remote-Client zugänglich gemacht werden.
Wichtig ist, dass der foreignfetch
-Handler beim Ausführen Zugriff auf alle Anmeldedaten und die Umgebungsautorität des Ursprungs hat, auf dem der Dienst-Worker gehostet wird. Als Entwickler, der einen externen, abruffähigen Dienst-Worker implementiert, liegt es in Ihrer Verantwortung, dafür zu sorgen, dass keine privilegierten Antwortdaten gehackt werden, die andernfalls aufgrund dieser Anmeldedaten nicht verfügbar wären. Die Aktivierung von CORS-Antworten ist ein Schritt, um die unbeabsichtigte Offenlegung zu verhindern. Als Entwickler können Sie jedoch explizit fetch()
-Anfragen innerhalb Ihres foreignfetch
-Handlers senden, bei denen nicht die impliziten Anmeldedaten verwendet werden. Das geht so:
self.addEventListener('foreignfetch', event => {
// The new Request will have credentials omitted by default.
const noCredentialsRequest = new Request(event.request.url);
event.respondWith(
// Replace with your own request logic as appropriate.
fetch(noCredentialsRequest)
.catch(() => caches.match(noCredentialsRequest))
.then(response => ({response}))
);
});
Hinweise zu Clients
Es gibt einige zusätzliche Aspekte, die sich darauf auswirken, wie der Worker des externen Abrufdiensts Anfragen von Clients Ihres Dienstes verarbeitet.
Kunden mit einem eigenen Service Worker
Einige Kunden Ihres Dienstes haben möglicherweise bereits einen eigenen Service Worker, der Anfragen verarbeitet, die von ihrer Webanwendung stammen. Was bedeutet das für Ihren externen Service Worker für den Abruf von Inhalten?
Die fetch
-Handler in einem Erstanbieter-Service-Worker erhalten die erste Gelegenheit, auf alle von der Webanwendung gestellten Anfragen zu antworten, auch wenn es einen Service Worker eines Drittanbieters mit aktiviertem foreignfetch
und einem Bereich gibt, der die Anfrage abdeckt. Clients mit eigenen Service Workern können jedoch weiterhin Ihren externen Fetch-Service Worker nutzen.
Wenn Sie in einem selbstverwalteten Service Worker fetch()
zum Abrufen plattformübergreifender Ressourcen verwenden, wird der entsprechende externe Abruf-Service Worker ausgelöst. Das bedeutet, dass Code wie der folgende von Ihrem foreignfetch
-Handler profitieren kann:
// Inside a client's first-party service-worker.js:
self.addEventListener('fetch', event => {
// If event.request is under your foreign fetch service worker's
// scope, this will trigger your foreignfetch handler.
event.respondWith(fetch(event.request));
});
Wenn es auch Abruf-Handler von Drittanbietern gibt, die event.respondWith()
aber nicht beim Abwickeln von Anfragen für Ihre ressourcenübergreifende Ressource aufrufen, wird die Anfrage automatisch an Ihren foreignfetch
-Handler weitergeleitet:
// Inside a client's first-party service-worker.js:
self.addEventListener('fetch', event => {
if (event.request.mode === 'same-origin') {
event.respondWith(localRequestLogic(event.request));
}
// Since event.respondWith() isn't called for cross-origin requests,
// any foreignfetch handlers scoped to the request will get a chance
// to provide a response.
});
Wenn ein fetch
-Handler von Drittanbietern event.respondWith()
aufruft, aber fetch()
nicht verwendet, um eine Ressource im Bereich des externen Abrufs anzufordern, kann der Service Worker für den externen Abruf die Anfrage nicht verarbeiten.
Clients, die keinen eigenen Service Worker haben
Alle Clients, die Anfragen an einen Drittanbieterdienst senden, können davon profitieren, wenn der Dienst einen fremden Fetch-Service-Worker bereitstellt, auch wenn sie nicht bereits einen eigenen Service Worker verwenden. Clients müssen nichts Bestimmtes tun, um die Verwendung eines externen Fetch-Dienst-Workers zu aktivieren, solange sie einen Browser verwenden, der dies unterstützt. Wenn Sie also einen externen Fetch-Dienstworker bereitstellen, profitieren viele der Kunden Ihres Dienstes sofort von Ihrer benutzerdefinierten Anfragelogik und dem gemeinsamen Cache, ohne dass sie weitere Schritte ausführen müssen.
Alles zusammenfassen: Wo Kunden nach einer Antwort suchen
Unter Berücksichtigung der oben genannten Informationen können wir eine Hierarchie von Quellen erstellen, die ein Client verwendet, um eine Antwort für eine plattformübergreifende Anfrage zu finden.
fetch
-Handler eines eigenen Service Workers (falls vorhanden)- Der
foreignfetch
-Handler eines Drittanbieter-Diensteworkers (falls vorhanden und nur für plattformübergreifende Anfragen) - Der HTTP-Cache des Browsers (falls eine aktuelle Antwort vorhanden ist)
- Das Netzwerk
Der Browser beginnt oben und geht je nach Service Worker-Implementierung die Liste so lange durch, bis er eine Quelle für die Antwort findet.
Weitere Informationen
Bleiben Sie auf dem Laufenden
Die Implementierung des Ursprungstests für ausländische Abrufe in Chrome kann sich aufgrund von Feedback von Entwicklern ändern. Wir halten diesen Beitrag über Inline-Änderungen auf dem neuesten Stand und notieren die konkreten Änderungen unten, sobald sie in Kraft treten. Informationen zu wichtigen Änderungen werden auch über das Twitter-Konto @chromiumdev veröffentlicht.