Service worker multiorigine - Esperimento con il recupero estero

Jacopo Posnick
Jacopo Posnick

Contesto

I service worker consentono agli sviluppatori web di rispondere alle richieste di rete effettuate dalle loro applicazioni web, consentendo loro di continuare a lavorare anche in modalità offline, di combattere il lie-fi e di implementare interazioni complesse con la cache come stale-while-revalidate. Tuttavia, i Service worker sono storicamente associati a un'origine specifica: in quanto proprietario di un'app web, è tua responsabilità scrivere ed eseguire il deployment di un service worker per intercettare tutte le richieste di rete effettuate dalla tua applicazione web. In questo modello, ogni service worker è responsabile della gestione anche delle richieste multiorigine, ad esempio inviate a un'API di terze parti o a caratteri web.

Cosa succederebbe se un provider di terze parti di un'API, caratteri web o altri servizi di uso comune avesse la possibilità di eseguire il deployment del proprio service worker che ha la possibilità di gestire le richieste effettuate da altre origini alla propria origine? I provider possono implementare la propria logica di networking personalizzata e sfruttare un'unica istanza di cache autorevole per archiviare le risposte. Ora, grazie al recupero esterno, quel tipo di deployment dei service worker di terze parti è una realtà.

L'implementazione di un service worker che implementa il recupero esterno ha senso per qualsiasi provider di un servizio a cui si accede tramite richieste HTTPS dai browser. Pensa solo agli scenari in cui potresti fornire una versione del servizio indipendente dalla rete, in cui i browser potrebbero sfruttare una comune cache di risorse. I servizi che potrebbero trarre vantaggio da questa situazione includono, a titolo esemplificativo:

  • Fornitori di API con interfacce RESTful
  • Fornitori di caratteri web
  • Provider di analisi dei dati
  • Provider host di immagini
  • Reti CDN (Content Delivery Network) generiche

Immagina, ad esempio, di essere un fornitore di soluzioni di analisi. Eseguendo il deployment di un service worker di recupero straniero, puoi assicurarti che tutte le richieste al tuo servizio non riuscite mentre un utente è offline vengano messe in coda e riprodotte quando viene ripristinata la connettività. Sebbene sia stato possibile per i client di un servizio implementare un comportamento simile tramite service worker proprietari, richiedere a ogni singolo client di scrivere una logica su misura per il tuo servizio non è scalabile quanto affidarsi a un service worker di recupero straniero condiviso di cui esegui il deployment.

Prerequisiti

Token della prova dell'origine

Il recupero esterno è ancora considerato sperimentale. Per evitare che questo design venga integrato prematuramente prima che sia completamente specificato e approvato dai fornitori dei browser, è stato implementato in Chrome 54 come prova dell'origine. Finché il recupero dei dati esterni rimane sperimentale, per utilizzare questa nuova funzionalità con il servizio che ospiti dovrai richiedere un token limitato all'origine specifica del servizio. Il token deve essere incluso come intestazione della risposta HTTP in tutte le richieste multiorigine per le risorse che vuoi gestire tramite recupero esterno, nonché nella risposta per la risorsa JavaScript del service worker:

Origin-Trial: token_obtained_from_signup

La prova terminerà a marzo 2017. A quel punto, ci aspettiamo di aver individuato le modifiche necessarie per stabilizzare la funzionalità e (si spera) di attivarla per impostazione predefinita. Se il recupero esterno non è abilitato per impostazione predefinita entro questa data, la funzionalità associata ai token delle prove dell'origine esistenti smetterà di funzionare.

Per facilitare gli esperimenti con il recupero dei dati dall'esterno prima di registrare un token ufficiale della prova dell'origine, puoi ignorare il requisito di Chrome per il tuo computer locale andando alla pagina chrome://flags/#enable-experimental-web-platform-features e attivando il flag "Funzionalità sperimentali della piattaforma web". Tieni presente che questa operazione deve essere eseguita in ogni istanza di Chrome che vuoi utilizzare nelle tue sperimentazioni locali, mentre con un token di prova dell'origine la funzionalità sarà disponibile per tutti i tuoi utenti di Chrome.

HTTPS

Come per tutti i deployment dei service worker, il server web che utilizzi per gestire sia le risorse sia lo script del service worker deve essere accessibile tramite HTTPS. Inoltre, l'intercettazione dei fetch esterni si applica solo alle richieste che provengono da pagine ospitate su origini sicure, quindi i client del tuo servizio devono utilizzare HTTPS per sfruttare l'implementazione di fetch esterni.

Utilizzo del recupero esterno

Esaminiamo i dettagli tecnici necessari per rendere operativo un service worker di recupero straniero.

Registrazione del service worker

La prima sfida che probabilmente ti imbatterai è come registrare il service worker. Se hai già lavorato con i Service worker, probabilmente conosci i seguenti argomenti:

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

Questo codice JavaScript per la registrazione di un service worker proprietario ha senso nel contesto di un'app web e viene attivato da un utente che visita un URL controllato da te. Tuttavia, non è un approccio attuabile per registrare un service worker di terze parti, quando l'unica interazione che il browser avrà con il server è la richiesta di una sottorisorsa specifica e non di una navigazione completa. Se il browser richiede, ad esempio, un'immagine da un server CDN che gestisci, non puoi anteporre questo snippet di JavaScript alla tua risposta e aspettarti che venga eseguito. È richiesto un metodo diverso di registrazione dei service worker, al di fuori del normale contesto di esecuzione JavaScript.

La soluzione è un'intestazione HTTP che il server può includere in qualsiasi risposta:

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

Analizziamo l'intestazione di esempio nei suoi componenti, ciascuno dei quali è separato da un carattere ;.

  • </service-worker.js> è obbligatorio e viene utilizzato per specificare il percorso del file del service worker (sostituisci /service-worker.js con il percorso appropriato dello script). Corrisponde direttamente alla stringa scriptURL che altrimenti verrebbe passata come primo parametro a navigator.serviceWorker.register(). Il valore deve essere racchiuso tra caratteri <> (come richiesto dalla specifica dell'intestazione Link) e se viene fornito un URL relativo anziché un URL assoluto, verrà interpretato come relativo alla posizione della risposta.
  • È richiesto anche rel="serviceworker", che deve essere incluso senza necessità di personalizzazione.
  • scope=/ è una dichiarazione di ambito facoltativa, equivalente alla stringa options.scope che puoi trasmettere come secondo parametro a navigator.serviceWorker.register(). In molti casi d'uso, non è un problema utilizzare l'ambito predefinito, quindi non esitare a ometterlo se non hai la certezza di averne bisogno. Alle registrazioni dell'intestazione Link valgono le stesse restrizioni sull'ambito massimo consentito, insieme alla possibilità di allentare tali limitazioni tramite l'intestazione Service-Worker-Allowed.

Proprio come per la registrazione di un service worker "tradizionale", l'utilizzo dell'intestazione Link installerà un service worker che verrà utilizzato per la richiesta successiva effettuata nell'ambito registrato. Il corpo della risposta che include l'intestazione speciale verrà utilizzato così com'è ed è subito disponibile per la pagina, senza attendere che il service worker straniero termini l'installazione.

Ricorda che il recupero dei dati dall'estero è attualmente implementato come prova dell'origine, pertanto, oltre all'intestazione della risposta del link, dovrai includere anche un'intestazione Origin-Trial valida. L'insieme minimo di intestazioni della risposta da aggiungere per registrare il service worker di recupero straniero è

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

Registrazione debug

Durante lo sviluppo, è consigliabile confermare che il service worker di recupero straniero sia installato correttamente ed elabora le richieste. Esistono alcuni controlli che puoi controllare negli Strumenti per sviluppatori di Chrome per verificare che tutto funzioni come previsto.

Vengono inviate intestazioni di risposta corrette?

Per registrare il service worker di recupero straniero, devi impostare un'intestazione Link in una risposta a una risorsa ospitata sul tuo dominio, come descritto in precedenza in questo post. Durante il periodo di prova dell'origine, supponendo che tu non abbia impostato chrome://flags/#enable-experimental-web-platform-features, devi impostare anche un'intestazione della risposta Origin-Trial. Per verificare che queste intestazioni siano impostate dal server web, controlla la voce nel riquadro Network (Rete) di DevTools:

Intestazioni visualizzate nel riquadro Rete.

Il service worker di recupero esterno è registrato correttamente?

Puoi anche verificare la registrazione del service worker sottostante, incluso il suo ambito, esaminando l'elenco completo dei service worker nel riquadro Applicazione di DevTools. Assicurati di selezionare l'opzione "Mostra tutti", poiché per impostazione predefinita vengono visualizzati solo i service worker per l'origine corrente.

Il service worker di recupero straniero nel riquadro Applicazioni.

Gestore di eventi di installazione

Ora che hai registrato il service worker di terze parti, avrà la possibilità di rispondere agli eventi install e activate, proprio come farebbe qualsiasi altro service worker. Può sfruttare questi eventi, ad esempio, per completare le cache con le risorse richieste durante l'evento install o eliminare le cache obsolete nell'evento activate.

Oltre alle normali attività install di memorizzazione nella cache degli eventi, c'è un passaggio aggiuntivo obbligatorio all'interno del gestore di eventi install del service worker di terze parti. Il tuo codice deve chiamare registerForeignFetch(), come nell'esempio seguente:

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

Sono disponibili due opzioni di configurazione, entrambe obbligatorie:

  • scopes prende un array di una o più stringhe, ognuna delle quali rappresenta un ambito per le richieste che attiveranno un evento foreignfetch. Un momento, potresti pensare Ho già definito un ambito durante la registrazione del service worker. Questo è vero e l'ambito complessivo è ancora pertinente: ogni ambito specificato qui deve essere uguale o sotto-ambito dell'ambito complessivo del service worker. Le limitazioni di ambito aggiuntive qui riportate ti consentono di implementare un service worker multiuso in grado di gestire sia gli eventi fetch proprietari (per le richieste effettuate dal tuo sito) sia gli eventi foreignfetch di terze parti (per le richieste effettuate da altri domini) e chiarire che solo un sottoinsieme dell'ambito più ampio deve attivare foreignfetch. In pratica, se esegui il deployment di un service worker dedicato alla gestione solo di eventi foreignfetch di terze parti, ti consigliamo di utilizzare un singolo ambito esplicito uguale all'ambito complessivo del service worker. Questo è ciò che accade nell'esempio precedente, utilizzando il valore self.registration.scope.
  • origins prende anche un array di una o più stringhe e ti consente di limitare il gestore foreignfetch in modo che risponda solo alle richieste provenienti da domini specifici. Ad esempio, se consenti esplicitamente "https://example.com", la richiesta effettuata da una pagina ospitata in https://example.com/path/to/page.html per una risorsa fornita dall'ambito di recupero esterno attiverà il gestore del recupero esterno, ma le richieste effettuate da https://random-domain.com/path/to/page.html non attiveranno il gestore. A meno che tu non abbia un motivo specifico per attivare la logica di recupero esterna solo per un sottoinsieme di origini remote, puoi specificare semplicemente '*' come unico valore nell'array; tutte le origini saranno consentite.

Gestore di eventi extra-fetch

Ora che hai installato il service worker di terze parti ed è stato configurato tramite registerForeignFetch(), potrai intercettare richieste di sottorisorse multiorigine inviate al tuo server che rientrano nell'ambito di recupero esterno.

In un service worker proprietario tradizionale, ogni richiesta attivava un evento fetch a cui il service worker ha avuto la possibilità di rispondere. Il nostro service worker di terze parti ha la possibilità di gestire un evento leggermente diverso, chiamato foreignfetch. Concettualmente, i due eventi sono molto simili e ti danno l'opportunità di esaminare la richiesta in arrivo e, facoltativamente, di fornire una risposta tramite 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']
        };
    })
    );
});

Nonostante le somiglianze concettuali, ci sono alcune differenze nella pratica quando si chiama respondWith() su un ForeignFetchEvent. Anziché fornire semplicemente un Response (o un Promise che si risolve con un Response) a respondWith(), come si fa con FetchEvent, devi passare un Promise che si risolve con un oggetto con proprietà specifiche al respondWith() di ForeignFetchEvent:

  • response è obbligatorio e deve essere impostato sull'oggetto Response che verrà restituito al client che ha effettuato la richiesta. Se fornisci un valore diverso da un valore Response valido, la richiesta del client verrà terminata con un errore di rete. A differenza di quando chiami respondWith() all'interno di un gestore di eventi fetch, devi fornire un Response qui, non un Promise che si risolve con un Response. Puoi costruire la tua risposta tramite una catena di promesse e trasmetterla come parametro all'elemento respondWith() di foreignfetch, ma la catena deve risolversi con un oggetto contenente la proprietà response impostata su un oggetto Response. Puoi vederne una dimostrazione nell'esempio di codice riportato sopra.
  • origin è facoltativo e viene utilizzato per determinare se la risposta restituita è opaca. Se ometti questo parametro, la risposta sarà opaca e il client avrà accesso limitato al corpo e alle intestazioni della risposta. Se la richiesta è stata effettuata con mode: 'cors', la restituzione di una risposta opaca verrà considerata un errore. Tuttavia, se specifichi un valore stringa uguale all'origine del client remoto (che può essere ottenuto tramite event.origin), attivi esplicitamente l'invio di una risposta abilitata per CORS al client.
  • Anche headers è facoltativo ed è utile solo se stai specificando anche origin e restituendo una risposta CORS. Per impostazione predefinita, vengono incluse nella risposta solo le intestazioni nell'elenco di intestazioni delle risposte inserite nella lista sicura CORS. Se devi filtrare ulteriormente gli elementi restituiti, le intestazioni prendono un elenco di uno o più nomi di intestazioni e li utilizzeranno come lista consentita delle intestazioni da esporre nella risposta. In questo modo puoi attivare CORS, evitando che le intestazioni delle risposte potenzialmente sensibili vengano esposte direttamente al client remoto.

È importante notare che, quando viene eseguito, il gestore foreignfetch ha accesso a tutte le credenziali e all'autorità ambientale dell'origine che ospita il service worker. In qualità di sviluppatore che esegue il deployment di un service worker straniero abilitato per il recupero, è tua responsabilità assicurarti di non divulgare dati di risposte con privilegi che non sarebbero altrimenti disponibili in base a queste credenziali. La richiesta di attivare le risposte CORS è un passaggio per limitare l'esposizione involontaria, ma in qualità di sviluppatore puoi effettuare esplicitamente richieste fetch() all'interno del gestore foreignfetch che non utilizzano le credenziali implicite tramite:

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

Considerazioni sui clienti

Esistono alcune considerazioni aggiuntive che influiscono sul modo in cui il service worker di recupero straniero gestisce le richieste effettuate dai client del tuo servizio.

Client che hanno un proprio service worker proprietario

Alcuni client del tuo servizio potrebbero avere già un proprio service worker proprietario che gestisce le richieste provenienti dalla loro app web. Cosa cambia per il service worker di recupero straniero di terze parti?

I gestori fetch in un service worker proprietario hanno la prima opportunità di rispondere a tutte le richieste effettuate dall'app web, anche se è presente un service worker di terze parti con foreignfetch abilitato e con un ambito che copre la richiesta. Tuttavia, i clienti con service worker proprietari possono comunque trarre vantaggio dal service worker di recupero straniero.

All'interno di un service worker proprietario, l'utilizzo di fetch() per recuperare le risorse multiorigine attiverà il service worker di recupero esterno appropriato. Ciò significa che un codice come il seguente può sfruttare il tuo gestore foreignfetch:

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

Allo stesso modo, se esistono gestori di recupero proprietari che, però, non chiamano event.respondWith() durante la gestione delle richieste per la risorsa multiorigine, la richiesta "rientra" automaticamente nel gestore foreignfetch:

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

Se un gestore fetch proprietario chiama event.respondWith() ma non utilizza fetch() per richiedere una risorsa nell'ambito del recupero esterno, il tuo service worker di recupero esterno non avrà la possibilità di gestire la richiesta.

Client che non dispongono di un proprio service worker

Tutti i client che effettuano richieste a un servizio di terze parti possono trarre vantaggio dal deployment di un service worker di recupero straniero, anche se non stanno già utilizzando un proprio service worker. I client non devono fare nulla di specifico per attivare l'utilizzo di un service worker straniero di recupero, purché utilizzino un browser che lo supporta. Ciò significa che, se esegui il deployment di un service worker di recupero straniero, la logica di richiesta personalizzata e la cache condivisa andranno a vantaggio immediatamente di molti client del tuo servizio, senza che debbano seguire ulteriori passaggi.

Riassumendo: dove i clienti cercano una risposta

Tenendo conto delle informazioni precedenti, possiamo assemblare una gerarchia di origini che un client utilizzerà per trovare una risposta per una richiesta multiorigine.

  1. Gestore fetch del service worker proprietario (se presente)
  2. Gestore foreignfetch di un service worker di terze parti (se presente e solo per le richieste multiorigine)
  3. La cache HTTP del browser (se esiste una risposta aggiornata)
  4. La rete

Il browser parte dall'alto e, a seconda dell'implementazione del service worker, continua in basso nell'elenco finché non trova un'origine per la risposta.

Scopri di più

Non perderti gli aggiornamenti

L'implementazione da parte di Chrome della prova dell'origine del recupero straniera è soggetta a modifiche man mano che rispondiamo al feedback degli sviluppatori. Aggiorneremo questo post tramite modifiche in linea e prenderemo nota delle modifiche specifiche di seguito man mano che vengono apportate. Condivideremo le informazioni sulle principali modifiche anche tramite l'account Twitter @chromiumdev.