Hintergrund
Service Worker geben Webentwicklern die Möglichkeit, auf Netzwerkanfragen von ihren Webanwendungen zu reagieren. So können sie auch offline weiterarbeiten, Lie-Fi bekämpfen und komplexe Cache-Interaktionen wie stale-while-revalid implementieren. In der Vergangenheit waren Service Worker 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. Bei diesem Modell ist jeder Service Worker auch für die Verarbeitung von ursprungsübergreifenden Anfragen verantwortlich, beispielsweise an eine Drittanbieter-API oder an Webschriftarten.
Was wäre, wenn ein Drittanbieter einer API, von Webschriftarten oder eines anderen häufig verwendeten Dienstes die Möglichkeit hätte, einen eigenen Service Worker bereitzustellen, der die Möglichkeit hätte, Anfragen von anderen Ursprüngen zu verarbeiten? Anbieter können ihre eigene benutzerdefinierte Netzwerklogik implementieren und eine einzelne, autoritative Cache-Instanz zum Speichern ihrer Antworten nutzen. Dank des Fremdabrufs werden Service Workers von Drittanbietern bereitgestellt.
Die Bereitstellung eines Service Workers, der fremde Abrufe implementiert, ist für alle Anbieter eines Dienstes sinnvoll, auf die über HTTPS-Anfragen von Browsern zugegriffen wird. Denken Sie nur an Szenarien, in denen Sie eine netzwerkunabhängige Version Ihres Dienstes bereitstellen könnten, bei der Browser einen gemeinsamen Ressourcen-Cache nutzen könnten. Folgende Dienste können unter anderem von diesen Vorteilen profitieren:
- API-Anbieter mit RESTful
- Anbieter von Webschriftarten
- Analyseanbieter
- Bildhosting-Anbieter
- Allgemeine Content Delivery Networks
Stellen Sie sich vor, Sie sind ein Analyseanbieter. Durch die Bereitstellung eines externen Abrufdienst-Workers können Sie dafür sorgen, dass alle Anfragen an Ihren Dienst, die fehlschlagen, während ein Nutzer offline ist, in die Warteschlange gestellt und wiederholt werden, sobald die Verbindung wiederhergestellt ist. Es ist zwar möglich, dass die Clients eines Dienstes ein ähnliches Verhalten über eigene Service Worker implementieren, aber jeder einzelne Client muss eine maßgeschneiderte Logik für Ihren Dienst schreiben. Das ist allerdings nicht so skalierbar wie die Nutzung eines freigegebenen fremden Abrufdienst-Workers, den Sie bereitstellen.
Voraussetzungen
Ursprüngliches Testtoken
Foreign Fetch gilt immer noch als experimentell. Um zu vermeiden, dass dieses Design verfrüht integriert wird, bevor es vollständig spezifiziert und von den Browseranbietern vereinbart wurde, wurde es in Chrome 54 als Ursprungstest implementiert. Solange der Fremdabruf noch experimentell ist, müssen Sie ein Token anfordern, das auf den spezifischen Ursprung Ihres Dienstes ausgerichtet ist, um diese neue Funktion mit dem von Ihnen gehosteten Dienst verwenden zu können. Das Token sollte als HTTP-Antwortheader in alle ursprungsübergreifenden Anfragen für Ressourcen aufgenommen werden, die Sie über einen externen 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 identifiziert und diese (hoffentlich) standardmäßig aktiviert haben. Wenn der Fremdabruf bis dahin nicht standardmäßig aktiviert ist, funktioniert die Funktion für vorhandene Ursprungstests-Tokens nicht mehr.
Um das Experimentieren mit ausländischen Abrufvorgängen vor der Registrierung eines offiziellen Ursprungstests-Tokens 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“ (Experimentelle Webplattformfunktionen). Dies muss in jeder Instanz von Chrome erfolgen, die Sie in Ihren lokalen Tests verwenden möchten. Mit einem Ursprungstest-Token ist die Funktion hingegen für alle Ihre Chrome-Nutzer verfügbar.
HTTPS
Wie bei allen Service Worker-Bereitstellungen muss der Zugriff auf den Webserver, den Sie für die Bereitstellung Ihrer Ressourcen verwenden, und das Service Worker-Skript über HTTPS erfolgen. Außerdem gilt das Abfangen von ausländischen Abruf nur für Anfragen von Seiten, die auf sicheren Ursprüngen gehostet werden. Die Clients Ihres Dienstes müssen also HTTPS verwenden, um Ihre fremde Abrufimplementierung nutzen zu können.
Foreign Fetch verwenden
Sehen wir uns nun die technischen Details an, die erforderlich sind, um einen externen Abrufdienst-Worker einzurichten und auszuführen.
Service Worker registrieren
Die erste Herausforderung, auf die Sie wahrscheinlich stoßen werden, ist die Registrierung Ihres Service Workers. Wenn Sie bereits mit Service Workern gearbeitet haben, sind Sie wahrscheinlich mit Folgendem vertraut:
// You can't do this!
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js');
}
Dieser JavaScript-Code für die Registrierung von Service Workern als Erstanbieter ist im Kontext einer Web-App sinnvoll. Er wird ausgelöst, wenn ein Nutzer zu einer von Ihnen verwalteten URL navigiert. Es ist jedoch kein praktikabler Ansatz für die Registrierung eines Service Workers von Drittanbietern, wenn die einzige Interaktion des Browsers mit Ihrem Server darin besteht, eine bestimmte Unterressource anzufordern, keine vollständige Navigation. Wenn der Browser beispielsweise ein Bild von einem von Ihnen verwalteten CDN-Server anfordert, können Sie dieses JavaScript-Snippet nicht Ihrer Antwort voranstellen und erwarten, dass es ausgeführt wird. Eine andere Methode für die Service Worker-Registrierung ist außerhalb des normalen JavaScript-Ausführungskontexts erforderlich.
Die Lösung besteht in Form eines HTTP-Headers, den Ihr Server in jede Antwort aufnehmen kann:
Link: </service-worker.js>; rel="serviceworker"; scope="/"
Wir schlüsseln diesen Beispielheader in seine Komponenten auf, die alle durch ein ;
-Zeichen getrennt sind.
</service-worker.js>
ist erforderlich und wird verwendet, um den Pfad zu Ihrer Service Worker-Datei anzugeben. Ersetzen Sie dabei/service-worker.js
durch den entsprechenden Pfad zu Ihrem Skript. Dies entspricht direkt dem StringscriptURL
, der andernfalls als erster Parameter annavigator.serviceWorker.register()
übergeben würde. Der Wert muss gemäß derLink
-Header-Spezifikation in<>
-Zeichen enthalten sein. Wird eine relative statt einer absoluten URL angegeben, wird sie als relativ zur Position der Antwort interpretiert.rel="serviceworker"
ist ebenfalls erforderlich und sollte ohne Anpassung enthalten sein.scope=/
ist eine optionale Bereichsdeklaration, was dem Stringoptions.scope
entspricht, den Sie als zweiten Parameter annavigator.serviceWorker.register()
übergeben können. In vielen Anwendungsfällen ist die Verwendung des Standardumfangs für Sie ausreichend. Lassen Sie dies also weg, sofern Sie nicht wissen, dass Sie ihn benötigen. FürLink
-Header-Registrierungen gelten die gleichen Einschränkungen hinsichtlich des maximal zulässigen Bereichs sowie die Möglichkeit, diese Einschränkungen über denService-Worker-Allowed
-Header zu lockern.
Genau wie bei einer „traditionellen“ Service Worker-Registrierung wird durch die Verwendung des Link
-Headers ein Service Worker installiert, der für die nächste Anfrage für den registrierten Bereich verwendet wird. Der Text der Antwort, der den speziellen Header enthält, wird unverändert verwendet und ist sofort für die Seite verfügbar, ohne darauf warten zu müssen, dass der Fremddienstmitarbeiter die Installation abgeschlossen hat.
Da der Abruf aus anderen Quellen derzeit als Ursprungstest implementiert wird, musst du neben dem Header der Linkantwort auch einen gültigen Origin-Trial
-Header angeben. Die Mindestanzahl von Antwortheadern, die hinzugefügt werden müssen, um deinen fremdsprachigen Abrufdienst-Worker zu registrieren, ist
Link: </service-worker.js>; rel="serviceworker"
Origin-Trial: token_obtained_from_signup
Fehler bei der Registrierung beheben
Während der Entwicklung sollten Sie überprüfen, ob Ihr fremder Abrufdienst-Worker ordnungsgemäß installiert ist und Anfragen verarbeitet. In den Entwicklertools von Chrome können Sie überprüfen, ob alles wie erwartet funktioniert.
Werden die richtigen Antwortheader gesendet?
Um den externen Abrufdienst-Worker zu registrieren, müssen Sie einen Link-Header für eine Antwort auf eine auf Ihrer Domain gehostete Ressource festlegen, wie weiter oben in diesem Post beschrieben. Während des Ursprungstests müssen Sie, sofern chrome://flags/#enable-experimental-web-platform-features
nicht festgelegt ist, auch einen Origin-Trial
-Antwortheader festlegen. Sie können überprüfen, ob Ihr Webserver diese Header festlegt, indem Sie sich den Eintrag im Bereich Netzwerk der Entwicklertools ansehen:
Ist der Foreign Fetch Service Worker ordnungsgemäß registriert?
Sie können auch die zugrunde liegende Service Worker-Registrierung einschließlich ihres Umfangs prüfen, indem Sie sich die vollständige Liste der Service Worker im Bereich Anwendung der Entwicklertools ansehen. Wählen Sie die Option „Alle anzeigen“ aus, da standardmäßig nur Service Worker für den aktuellen Ursprung angezeigt werden.
Event-Handler für die Installation
Nachdem Sie den externen Service Worker registriert haben, kann er auf die Ereignisse install
und activate
antworten, genau wie jeder andere Service Worker. Die Funktion kann diese Ereignisse nutzen, um beispielsweise während des install
-Ereignisses Caches mit den erforderlichen Ressourcen zu füllen oder veraltete Caches im activate
-Ereignis zu bereinigen.
Neben den üblichen Cache-Aktivitäten für install
-Ereignisse gibt es einen zusätzlichen Schritt im install
-Event-Handler des externen Service Workers. Ihr Code muss registerForeignFetch()
wie im folgenden Beispiel aufrufen:
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 aus einem oder mehreren Strings, von denen jeder einen Bereich für Anfragen darstellt, die einforeignfetch
-Ereignis auslösen. Moment mal, vielleicht denken Sie: Ich habe bei der Service Worker-Registrierung bereits einen Umfang definiert. Das stimmt und der Gesamtumfang ist dennoch relevant – jeder hier angegebene Umfang muss entweder dem Gesamtumfang des Service Workers entsprechen oder nur einem Teil davon entsprechen. Mit den zusätzlichen Bereichseinschränkungen können Sie einen Allzweck-Service Worker bereitstellen, der sowohl Erstanbieter-fetch
-Ereignisse (für Anfragen von Ihrer eigenen Website) als auchforeignfetch
-Drittanbieter-Ereignisse (für Anfragen von anderen Domains) verarbeiten kann und deutlich macht, dass nur ein Teil Ihres größeren Bereichsforeignfetch
auslösen sollte. Wenn Sie in der Praxis einen Service Worker bereitstellen, der ausschließlich für die Verarbeitung vonforeignfetch
-Ereignissen von Drittanbietern vorgesehen ist, empfiehlt es sich, nur einen einzelnen, expliziten Bereich zu verwenden, der dem Gesamtbereich Ihres Service Workers entspricht. Dazu wird im obigen Beispiel der Wertself.registration.scope
verwendet.origins
verwendet auch ein Array mit einem oder mehreren Strings und ermöglicht es Ihnen, denforeignfetch
-Handler so zu beschränken, dass er nur auf Anfragen von bestimmten Domains antwortet. Wenn du beispielsweise „https://beispiel.de“ ausdrücklich zulässt, löst eine Anfrage von einer unterhttps://example.com/path/to/page.html
gehosteten Seite für eine Ressource, die von deinem fremden Abrufbereich bereitgestellt wird, deinen fremdsprachigen Abruf-Handler aus. Anfragen vonhttps://random-domain.com/path/to/page.html
lösen deinen Handler jedoch nicht aus. Sofern Sie Ihre fremde Abruflogik nicht nur für einen Teil der Remote-Ursprünge auslösen möchten, können Sie einfach'*'
als einzigen Wert im Array angeben. Alle Ursprünge sind dann zulässig.
Ereignis-Handler für Fremdabruf
Nachdem Sie den externen Service Worker installiert und über registerForeignFetch()
konfiguriert haben, kann er ursprungsübergreifende Anfragen zu Unterressourcen an Ihren Server abfangen, die in den fremden Abrufbereich fallen.
Bei einem herkömmlichen eigenen Service Worker würde jede Anfrage ein fetch
-Ereignis auslösen, auf das der Service Worker antworten konnte. Unser Service Worker (Drittanbieter) hat die Möglichkeit, ein geringfügig anderes Ereignis namens foreignfetch
zu verarbeiten. Vom Konzept her sind die beiden Ereignisse sehr ähnlich. Sie geben Ihnen die Möglichkeit, die eingehende Anfrage zu prüfen und optional über respondWith()
eine Antwort darauf zu senden:
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 in der Praxis einige Unterschiede beim Aufrufen von respondWith()
für ein ForeignFetchEvent
. Anstatt einfach ein Response
(oder Promise
, das mit einem Response
aufgelöst wird) wie mit einem FetchEvent
anzugeben, musst du eine Promise
übergeben, die mit einem Objekt mit bestimmten Eigenschaften auf die respondWith()
des ForeignFetchEvent
aufgelöst wird:respondWith()
response
ist erforderlich und muss auf dasResponse
-Objekt festgelegt werden, das an den Client zurückgegeben wird, der die Anfrage gestellt hat. Wenn du andere Werte als eine gültigeResponse
angibst, wird die Clientanfrage mit einem Netzwerkfehler beendet. Anders als beim Aufrufen vonrespondWith()
in einemfetch
-Event-Handler müssen Sie hier einenResponse
angeben und keinenPromise
, der mitResponse
aufgelöst wird. Sie können Ihre Antwort über eine Promise-Kette erstellen und diese Kette als Parameter anrespondWith()
vonforeignfetch
übergeben. Die Kette muss jedoch mit einem Objekt aufgelöst werden, das dieresponse
-Eigenschaft und einResponse
-Objekt enthält. Ein Beispiel dafür finden Sie im obigen Codebeispiel.origin
ist optional und bestimmt, ob die zurückgegebene Antwort opaque ist. Wenn Sie diesen Wert weglassen, ist die Antwort intransparent und der Client hat nur eingeschränkten Zugriff auf den Text und die Header der Antwort. Wenn die Anfrage mitmode: 'cors'
erfolgte, wird die Rückgabe einer intransparenten Antwort als Fehler behandelt. Wenn Sie jedoch einen Stringwert angeben, der dem Ursprung des Remoteclients entspricht (der überevent.origin
abgerufen werden kann), wird explizit die Bereitstellung einer CORS-fähigen Antwort an den Client aktiviert.headers
ist ebenfalls optional und nur nützlich, wenn Sie auchorigin
angeben und eine CORS-Antwort zurückgeben. Standardmäßig werden nur Header aus der Liste der Antwortheader mit CORS-sicherer Liste in Ihre Antwort aufgenommen. Wenn Sie weiter filtern möchten, was zurückgegeben wird, wird für Header eine Liste mit einem oder mehreren Headernamen verwendet, die als Zulassungsliste für die Header verwendet wird, die in der Antwort angezeigt werden sollen. So können Sie CORS aktivieren und gleichzeitig verhindern, dass potenziell vertrauliche Antwortheader direkt für den Remoteclient freigegeben werden.
Wichtig: Wenn der foreignfetch
-Handler ausgeführt wird, hat er Zugriff auf alle Anmeldedaten und Umgebungsbefugnisse des Ursprungs, der den Service Worker hostet. Wenn Sie als Entwickler einen Service Worker mit aktiviertem Abruf aus einem anderen Land bereitstellen, müssen Sie dafür sorgen, dass keine privilegierten Antwortdaten offengelegt werden, die sonst aufgrund dieser Anmeldedaten nicht verfügbar wären. Das Erfordern einer Zustimmung für CORS-Antworten ist nur ein Schritt, um eine unbeabsichtigte Offenlegung zu begrenzen. Als Entwickler können Sie jedoch fetch()
-Anfragen in Ihrem foreignfetch
-Handler explizit fetch()
senden, ohne die impliziten Anmeldedaten zu verwenden:
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}))
);
});
Überlegungen zu Clients
Es gibt einige zusätzliche Überlegungen, die sich darauf auswirken, wie Ihr fremder Abrufdienst-Worker Anfragen von Clients Ihres Dienstes verarbeitet.
Kunden mit einem eigenen First-Party Service Worker
Einige Clients Ihres Dienstes haben möglicherweise bereits einen eigenen First-Party-Service-Worker, der Anfragen aus ihrer Webanwendung verarbeitet. Was bedeutet das für Ihren externen, fremden Abrufdienst-Worker?
Die fetch
-Handler in einem eigenen Service Worker haben die erste Gelegenheit, auf alle Anfragen von der Web-App zu antworten, auch wenn es einen Service Worker eines Drittanbieters gibt, bei dem foreignfetch
aktiviert ist und der Bereich die Anfrage abdeckt. Kunden mit eigenen Service Workern können jedoch trotzdem von Ihrem fremden Abrufdienst-Worker profitieren.
In einem eigenen Service Worker wird durch die Verwendung von fetch()
zum Abrufen von ursprungsübergreifenden Ressourcen der entsprechende fremde Abrufdienst-Worker ausgelöst. Das bedeutet, dass Code wie der folgende Ihren foreignfetch
-Handler nutzen 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));
});
Ähnlich verhält es sich, wenn eigene Abruf-Handler vorhanden sind, die event.respondWith()
bei der Verarbeitung von Anfragen für deine ursprungsübergreifende Ressource jedoch nicht aufrufen. In diesem Fall „durchläuft“ die Anfrage automatisch deinen foreignfetch
-Handler:
// 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 eigener fetch
-Handler event.respondWith()
aufruft, aber fetch()
nicht verwendet, um eine Ressource in Ihrem fremden Abrufbereich anzufordern, hat Ihr fremder Abrufdienst-Worker keine Möglichkeit, die Anfrage zu verarbeiten.
Clients ohne eigenen Service Worker
Alle Clients, die Anfragen an einen Drittanbieterdienst senden, können von der Bereitstellung eines externen Abrufdienst-Workers profitieren, auch wenn sie nicht bereits ihren eigenen Service Worker verwenden. Clients müssen nichts unternehmen, um die Verwendung eines externen Abrufdienst-Workers zu aktivieren, solange sie einen Browser verwenden, der dies unterstützt. Das bedeutet, dass durch die Bereitstellung eines fremdsprachigen Abrufdienst-Workers Ihre benutzerdefinierte Anforderungslogik und der gemeinsam genutzte Cache viele Clients Ihres Dienstes sofort nutzen, ohne dass diese weitere Schritte ausführen müssen.
Zusammenfassung: Wo Kunden nach einer Antwort suchen
Unter Berücksichtigung der oben genannten Informationen können wir eine Hierarchie von Quellen zusammenstellen, die ein Client verwendet, um eine Antwort auf eine ursprungsübergreifende Anfrage zu finden.
- Der
fetch
-Handler eines eigenen Service Workers (falls vorhanden) - Der
foreignfetch
-Handler eines Service Workers eines Drittanbieters (falls vorhanden und nur für ursprungsübergreifende Anfragen) - Den HTTP-Cache des Browsers (falls eine neue Antwort vorhanden ist)
- Das Netzwerk
Der Browser beginnt von oben nach unten und fährt je nach Service Worker-Implementierung von oben nach unten, bis er eine Quelle für die Antwort findet.
Weitere Informationen
Bleibe auf dem neuesten Stand
Die Implementierung des Ursprungstests für den ausländischen Abruf in Chrome kann sich ändern, da wir auf Feedback von Entwicklern eingehen. Der Beitrag wird durch Inline-Änderungen auf dem neuesten Stand gehalten und die unten aufgeführten Änderungen werden in Kürze vorgenommen. Informationen zu wichtigen Änderungen senden wir auch über das Twitter-Konto @chromiumdev.