Cross-origin servicemedewerkers - Experimenteren met buitenlandse ophaalacties

Achtergrond

Servicemedewerkers geven webontwikkelaars de mogelijkheid om te reageren op netwerkverzoeken van hun webapplicaties, waardoor ze zelfs offline kunnen blijven werken, lie-fi kunnen bestrijden en complexe cache-interacties kunnen implementeren, zoals verouderde-whistle-revalidate . Maar servicemedewerkers zijn van oudsher gebonden aan een specifieke oorsprong: als eigenaar van een webapp is het uw verantwoordelijkheid om een ​​servicemedewerker te schrijven en in te zetten om alle netwerkverzoeken die uw webapp doet te onderscheppen. In dat model is elke servicemedewerker verantwoordelijk voor het afhandelen van zelfs cross-originele verzoeken, bijvoorbeeld naar een API van derden of voor weblettertypen.

Wat als een externe leverancier van een API, webfonts of een andere veelgebruikte dienst de macht zou hebben om zijn eigen servicemedewerker in te zetten, die de kans krijgt om verzoeken van andere bronnen naar hun bron af te handelen? Providers kunnen hun eigen aangepaste netwerklogica implementeren en profiteren van één enkele, gezaghebbende cache-instantie voor het opslaan van hun antwoorden. Dankzij de buitenlandse ophaalactie is dit soort inzet van externe dienstverleners nu werkelijkheid.

Het inzetten van een servicemedewerker die buitenlandse ophaalacties implementeert, is zinvol voor elke aanbieder van een service die toegankelijk is via HTTPS-verzoeken van browsers. Denk maar eens aan scenario's waarin u een netwerkonafhankelijke versie van uw service kunt bieden, waarin browsers kunnen profiteren van een gemeenschappelijke resourcecache. Diensten die hiervan kunnen profiteren omvatten, maar zijn niet beperkt tot:

  • API-providers met RESTful- interfaces
  • Aanbieders van weblettertypen
  • Analytics-aanbieders
  • Aanbieders van beeldhosting
  • Generieke netwerken voor het leveren van inhoud

Stel je bijvoorbeeld voor dat je een analyseprovider bent. Door een buitenlandse ophaalservicemedewerker in te zetten, kunt u ervoor zorgen dat alle aanvragen aan uw service die mislukken terwijl een gebruiker offline is, in de wachtrij worden geplaatst en opnieuw worden afgespeeld zodra de verbinding terugkeert. Hoewel het voor de klanten van een service mogelijk is geweest om soortgelijk gedrag te implementeren via eigen servicemedewerkers, is het niet zo schaalbaar om van elke klant te eisen dat hij op maat gemaakte logica voor uw service schrijft als vertrouwen op een gedeelde buitenlandse ophaalservicemedewerker die u inzet.

Vereisten

Origin-proeftoken

Buitenlandse ophaling wordt nog steeds als experimenteel beschouwd. Om te voorkomen dat dit ontwerp voortijdig wordt ingevoerd voordat het volledig is gespecificeerd en goedgekeurd door browserleveranciers, is het in Chrome 54 geïmplementeerd als een Origin-proefversie . Zolang buitenlandse ophaalacties experimenteel blijven, moet u, als u deze nieuwe functie wilt gebruiken met de service die u host, een token aanvragen dat is afgestemd op de specifieke oorsprong van uw service. Het token moet worden opgenomen als een HTTP-antwoordheader in alle cross-origin-aanvragen voor bronnen die u wilt verwerken via buitenlandse ophaalacties, evenals in het antwoord voor de JavaScript-bron van uw servicewerker:

Origin-Trial: token_obtained_from_signup

De proefperiode eindigt in maart 2017. Tegen die tijd verwachten we alle wijzigingen te hebben ontdekt die nodig zijn om de functie te stabiliseren en (hopelijk) standaard in te schakelen. Als buitenlands ophalen tegen die tijd niet standaard is ingeschakeld, werkt de functionaliteit die is gekoppeld aan bestaande Origin Trial-tokens niet meer.

Om het experimenteren met buitenlands ophalen te vergemakkelijken voordat u zich registreert voor een officieel Origin Trial-token, kunt u de vereiste in Chrome voor uw lokale computer omzeilen door naar chrome://flags/#enable-experimental-web-platform-features gaan en de optie " Experimentele webplatformfuncties'-vlag. Houd er rekening mee dat dit moet worden gedaan in elk exemplaar van Chrome dat u wilt gebruiken in uw lokale experimenten, terwijl met een Origin-proeftoken de functie beschikbaar zal zijn voor al uw Chrome-gebruikers.

HTTPS

Zoals bij alle service worker-implementaties moet de webserver die u gebruikt voor het bedienen van zowel uw bronnen als uw service worker-script toegankelijk zijn via HTTPS . Bovendien is het onderscheppen van buitenlandse ophaalacties alleen van toepassing op verzoeken die afkomstig zijn van pagina's die op een beveiligde oorsprong worden gehost. De clients van uw service moeten dus HTTPS gebruiken om te profiteren van uw implementatie van buitenlandse ophaalacties.

Buitenlandse ophaalactie gebruiken

Nu de vereisten achter de rug zijn, gaan we dieper in op de technische details die nodig zijn om een ​​buitenlandse ophaalservicemedewerker aan de slag te krijgen.

Uw servicemedewerker registreren

De eerste uitdaging waar u waarschijnlijk tegenaan zult lopen, is hoe u uw servicemedewerker moet registreren. Als u eerder met servicemedewerkers heeft gewerkt, bent u waarschijnlijk bekend met het volgende:

// You can't do this!
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('service-worker.js');
}

Deze JavaScript-code voor een directe registratie van servicemedewerkers is zinvol in de context van een web-app, geactiveerd doordat een gebruiker naar een URL navigeert die u beheert. Maar het is geen haalbare aanpak voor het registreren van een externe servicemedewerker, wanneer de enige interactie die browser met uw server heeft, het aanvragen van een specifieke subbron is, en niet een volledige navigatie. Als de browser bijvoorbeeld een afbeelding opvraagt ​​van een CDN-server die u onderhoudt, kunt u dat JavaScript-fragment niet aan uw antwoord toevoegen en verwachten dat het wordt uitgevoerd. Er is een andere registratiemethode voor servicemedewerkers vereist, buiten de normale JavaScript-uitvoeringscontext.

De oplossing komt in de vorm van een HTTP-header die uw server in elk antwoord kan opnemen:

Link: </service-worker.js>; rel="serviceworker"; scope="/"

Laten we die voorbeeldkop opsplitsen in zijn componenten, die elk worden gescheiden door een ; karakter.

  • </service-worker.js> is vereist en wordt gebruikt om het pad naar uw servicewerkerbestand op te geven (vervang /service-worker.js door het juiste pad naar uw script). Dit komt rechtstreeks overeen met de scriptURL tekenreeks die anders als eerste parameter aan navigator.serviceWorker.register() zou worden doorgegeven. De waarde moet worden omsloten door <> tekens (zoals vereist door de Link header-specificatie ), en als er een relatieve in plaats van een absolute URL wordt opgegeven, wordt deze geïnterpreteerd als relatief ten opzichte van de locatie van het antwoord .
  • rel="serviceworker" is ook vereist en moet worden opgenomen zonder dat er aanpassingen nodig zijn.
  • scope=/ is een optionele scopedeclaratie, equivalent aan de string options.scope die u als tweede parameter kunt doorgeven aan navigator.serviceWorker.register() . Voor veel gebruiksscenario's kunt u prima de standaard scope gebruiken , dus u kunt dit gerust weglaten, tenzij u weet dat u het nodig heeft. Dezelfde beperkingen rond het maximaal toegestane bereik, samen met de mogelijkheid om deze beperkingen te versoepelen via de Service-Worker-Allowed header, zijn van toepassing op Link headerregistraties.

Net als bij een "traditionele" registratie van servicemedewerkers, wordt met behulp van de Link header een servicemedewerker geïnstalleerd die zal worden gebruikt voor de volgende aanvraag die wordt gedaan voor het geregistreerde bereik. De hoofdtekst van het antwoord dat de speciale header bevat, wordt gebruikt zoals het is en is onmiddellijk beschikbaar op de pagina, zonder te wachten tot de buitenlandse servicemedewerker de installatie heeft voltooid.

Houd er rekening mee dat Foreign Fetch momenteel wordt geïmplementeerd als een Origin Trial , dus naast uw Link-antwoordheader moet u ook een geldige Origin-Trial header opnemen. De minimale set responsheaders die u moet toevoegen om uw buitenlandse ophaalservicemedewerker te registreren, is

Link: </service-worker.js>; rel="serviceworker"
Origin-Trial: token_obtained_from_signup

Registratie debuggen

Tijdens de ontwikkeling wilt u waarschijnlijk bevestigen dat uw buitenlandse ophaalservicemedewerker correct is geïnstalleerd en verzoeken verwerkt. Er zijn een paar dingen die u kunt controleren in de ontwikkelaarstools van Chrome om te bevestigen dat alles werkt zoals verwacht.

Worden de juiste antwoordheaders verzonden?

Om de buitenlandse ophaalservicemedewerker te registreren, moet u een Link-header instellen op een antwoord op een bron die op uw domein wordt gehost, zoals eerder in dit bericht beschreven. Tijdens de Origin-proefperiode, en ervan uitgaande dat je chrome://flags/#enable-experimental-web-platform-features niet hebt ingesteld, moet je ook een Origin-Trial antwoordheader instellen. U kunt bevestigen dat uw webserver deze headers instelt door naar de vermelding in het netwerkpaneel van DevTools te kijken:

Kopteksten weergegeven in het netwerkpaneel.

Is de Foreign Fetch-servicemedewerker correct geregistreerd?

U kunt ook de onderliggende registratie van servicemedewerkers, inclusief de reikwijdte ervan, bevestigen door de volledige lijst met servicemedewerkers te bekijken in het toepassingspaneel van DevTools. Zorg ervoor dat u de optie 'Alles weergeven' selecteert, aangezien u standaard alleen servicemedewerkers voor de huidige herkomst ziet.

De buitenlandse ophaalservicemedewerker in het paneel Toepassingen.

De installatiegebeurtenishandler

Nu u uw externe servicemedewerker heeft geregistreerd, krijgt deze de kans om te reageren op de install en activate , net zoals elke andere servicemedewerker zou doen. Het kan van deze gebeurtenissen profiteren om bijvoorbeeld caches te vullen met de vereiste bronnen tijdens de install , of verouderde caches op te schonen tijdens de activate .

Naast de normale activiteiten voor het cachen install , is er een extra stap vereist in de install van uw externe servicemedewerker. Uw code moet registerForeignFetch() aanroepen, zoals in het volgende voorbeeld:

self.addEventListener('install', event => {
    event.registerForeignFetch({
    scopes: [self.registration.scope], // or some sub-scope
    origins: ['*'] // or ['https://example.com']
    });
});

Er zijn twee configuratieopties, beide vereist:

  • scopes gebruikt een array van een of meer tekenreeksen, die elk een bereik vertegenwoordigen voor verzoeken die een foreignfetch gebeurtenis activeren. Maar wacht , je denkt misschien: ik heb al een bereik gedefinieerd tijdens de registratie van servicemedewerkers! Dat is waar, en dat algehele bereik is nog steeds relevant: elk bereik dat u hier opgeeft, moet gelijk zijn aan of een subbereik zijn van het algehele bereik van de servicemedewerker. Met de aanvullende bereikbeperkingen hier kunt u een servicemedewerker voor alle doeleinden inzetten die zowel fetch van de eerste partij (voor verzoeken van uw eigen site) als foreignfetch ophaalgebeurtenissen van derden (voor verzoeken van andere domeinen) kan afhandelen. het is duidelijk dat alleen een subset van uw grotere bereik foreignfetch zou moeten activeren. Als u een servicemedewerker inzet die zich uitsluitend bezighoudt met het afhandelen van foreignfetch ophaalgebeurtenissen van derden, wilt u in de praktijk slechts één expliciet bereik gebruiken dat gelijk is aan het algemene bereik van uw servicemedewerker. Dat is wat het bovenstaande voorbeeld zal doen, met behulp van de waarde self.registration.scope .
  • origins gebruikt ook een array van een of meer tekenreeksen en stelt u in staat uw foreignfetch handler te beperken zodat deze alleen reageert op verzoeken van specifieke domeinen. Als u bijvoorbeeld 'https://example.com' expliciet toestaat, wordt er een verzoek gedaan vanaf een pagina die wordt gehost op https://example.com/path/to/page.html voor een bron die wordt aangeboden vanuit uw buitenlandse ophaalbereik zal uw buitenlandse ophaalhandler activeren, maar verzoeken gedaan vanaf https://random-domain.com/path/to/page.html zullen uw handler niet activeren. Tenzij u een specifieke reden hebt om uw buitenlandse ophaallogica alleen te activeren voor een subset van externe oorsprongen, kunt u gewoon '*' opgeven als de enige waarde in de array, en alle oorsprongen zijn toegestaan.

De gebeurtenishandler Foreignfetch

Nu u uw externe servicemedewerker hebt geïnstalleerd en deze is geconfigureerd via registerForeignFetch() , krijgt deze de kans om cross-origin subresource-aanvragen naar uw server te onderscheppen die binnen het buitenlandse ophaalbereik vallen.

Bij een traditionele, first-party servicemedewerker zou elk verzoek een fetch activeren waarop uw servicemedewerker kon reageren. Onze externe servicemedewerker krijgt de kans om een ​​iets ander evenement af te handelen, genaamd foreignfetch . Conceptueel gezien lijken de twee gebeurtenissen behoorlijk op elkaar, en ze geven je de mogelijkheid om het binnenkomende verzoek te inspecteren en er optioneel een antwoord op te geven via respondWith() :

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']
        };
    })
    );
});

Ondanks de conceptuele overeenkomsten zijn er in de praktijk enkele verschillen bij het aanroepen van respondWith() op een ForeignFetchEvent . In plaats van alleen maar een Response (of Promise die wordt opgelost met een Response ) te geven aan respondWith() , zoals je doet met een FetchEvent , moet je een Promise die wordt opgelost met een object met specifieke eigenschappen doorgeven aan de respondWith() van ForeignFetchEvent :

  • response is vereist en moet worden ingesteld op het Response object dat wordt geretourneerd naar de client die het verzoek heeft ingediend. Als u iets anders opgeeft dan een geldig Response , wordt het verzoek van de client beëindigd met een netwerkfout. Anders dan bij het aanroepen van respondWith() binnen een fetch gebeurtenishandler, moet u hier een Response opgeven, en geen Promise die wordt opgelost met een Response ! U kunt uw antwoord construeren via een belofteketen en die keten doorgeven als parameter aan respondWith() van foreignfetch , maar de keten moet worden omgezet met een object dat de response bevat die is ingesteld op een Response -object. U kunt een demonstratie hiervan zien in het bovenstaande codevoorbeeld.
  • origin is optioneel en wordt gebruikt om te bepalen of het geretourneerde antwoord al dan niet opaque is. Als u dit weglaat, is het antwoord ondoorzichtig en heeft de client beperkte toegang tot de hoofdtekst en headers van het antwoord. Als het verzoek is gedaan met mode: 'cors' , wordt het retourneren van een ondoorzichtig antwoord als een fout behandeld. Als u echter een tekenreekswaarde opgeeft die gelijk is aan de oorsprong van de externe client (die kan worden verkregen via event.origin ), kiest u er expliciet voor om een ​​CORS-compatibel antwoord aan de client te geven.
  • headers zijn ook optioneel en zijn alleen nuttig als u ook origin opgeeft en een CORS-antwoord retourneert. Standaard worden alleen headers in de CORS-safelisted-antwoordheaderlijst opgenomen in uw antwoord. Als u verder wilt filteren wat er wordt geretourneerd, gebruikt headers een lijst met een of meer headernamen en gebruikt deze als een toelatingslijst met welke headers in het antwoord moeten worden weergegeven. Hierdoor kunt u zich aanmelden voor CORS en tegelijkertijd voorkomen dat potentieel gevoelige antwoordheaders rechtstreeks aan de externe client worden blootgesteld.

Het is belangrijk op te merken dat wanneer de foreignfetch handler wordt uitgevoerd, deze toegang heeft tot alle inloggegevens en omgevingsautoriteit van de oorsprong die als host fungeert voor de service worker . Als ontwikkelaar die een buitenlandse servicemedewerker met ophaalmogelijkheden inzet, is het uw verantwoordelijkheid om ervoor te zorgen dat u geen geprivilegieerde responsgegevens lekt die anders niet beschikbaar zouden zijn op grond van die inloggegevens. Het vereisen van een opt-in voor CORS-reacties is een stap om onbedoelde blootstelling te beperken, maar als ontwikkelaar kunt u expliciet fetch() -verzoeken doen binnen uw foreignfetch handler die de impliciete inloggegevens niet gebruiken via:

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}))
    );
});

Overwegingen van de klant

Er zijn enkele aanvullende overwegingen die van invloed zijn op de manier waarop uw buitenlandse ophaalservicemedewerker verzoeken van klanten van uw service afhandelt.

Klanten die hun eigen eerstelijnsservicemedewerker hebben

Sommige klanten van uw service hebben mogelijk al een eigen servicemedewerker die verzoeken afhandelt die afkomstig zijn van hun webapp. Wat betekent dit voor uw externe, buitenlandse ophaalservicemedewerker?

De fetch (s) in een first-party service worker krijgen de eerste kans om te reageren op alle verzoeken van de web-app, zelfs als er een externe service worker is waarbij foreignfetch is ingeschakeld met een bereik dat de aanvraag dekt. Maar klanten met eigen servicemedewerkers kunnen nog steeds profiteren van uw buitenlandse ophaalservicemedewerker!

Binnen een first-party servicemedewerker zal het gebruik van fetch() om cross-originebronnen op te halen, de juiste buitenlandse ophaalservicemedewerker activeren. Dat betekent dat code zoals de volgende kan profiteren van uw foreignfetch handler:

// 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));
});

Op dezelfde manier, als er first-party fetch-handlers zijn, maar deze event.respondWith() niet aanroepen bij het afhandelen van verzoeken voor uw cross-origin-bron, zal het verzoek automatisch "doorvallen" naar uw 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.
});

Als een first-party fetch event.respondWith() aanroept maar fetch() niet gebruikt om een ​​bron aan te vragen die binnen uw buitenlandse ophaalbereik valt, krijgt uw buitenlandse ophaalservicemedewerker geen kans om het verzoek af te handelen.

Klanten die geen eigen servicemedewerker hebben

Alle clients die aanvragen indienen bij een service van derden kunnen profiteren wanneer de service een buitenlandse ophaalservicemedewerker inzet, zelfs als ze nog niet hun eigen servicemedewerker gebruiken. Er is niets specifieks dat klanten hoeven te doen om zich aan te melden voor het gebruik van een buitenlandse ophaalservicemedewerker, zolang ze maar een browser gebruiken die dit ondersteunt. Dit betekent dat door het inzetten van een buitenlandse ophaalservicemedewerker, uw aangepaste aanvraaglogica en gedeelde cache veel van de klanten van uw service onmiddellijk ten goede zullen komen, zonder dat zij verdere stappen ondernemen.

Alles bij elkaar: waar klanten naar een antwoord zoeken

Rekening houdend met de bovenstaande informatie kunnen we een hiërarchie van bronnen samenstellen die een klant zal gebruiken om een ​​antwoord te vinden op een cross-origin verzoek.

  1. fetch van een eerstelijns servicemedewerker (indien aanwezig)
  2. De foreignfetch handler van een externe servicemedewerker (indien aanwezig, en alleen voor cross-origin-aanvragen)
  3. De HTTP-cache van de browser (als er een nieuw antwoord is)
  4. Het netwerk

De browser begint bovenaan en gaat, afhankelijk van de implementatie van de servicemedewerker, verder in de lijst totdat hij een bron voor het antwoord vindt.

Kom meer te weten

Blijf op de hoogte

De Chrome-implementatie van de Origin-proefversie voor buitenlandse ophaalacties kan worden gewijzigd omdat we rekening houden met feedback van ontwikkelaars. We houden dit bericht up-to-date via inline wijzigingen en zullen de specifieke wijzigingen hieronder noteren zodra ze zich voordoen. We delen ook informatie over grote wijzigingen via het @chromiumdev Twitter-account.