Nœuds de calcul de service multi-origines – Tester la récupération de données à l'étranger

Contexte

Les service workers permettent aux développeurs Web de répondre aux requêtes réseau émises par leurs applications Web. Ils peuvent ainsi continuer à travailler même en mode hors connexion, lutter contre le mensonge et implémenter des interactions de cache complexes telles que stale-while-revalidate. Cependant, les services workers ont toujours été associés à une origine spécifique. En tant que propriétaire d'une application Web, il vous incombe d'écrire et de déployer un service worker pour intercepter toutes les requêtes réseau de votre application Web. Dans ce modèle, chaque service worker est responsable de la gestion des requêtes inter-origines, par exemple vers une API tierce ou pour les polices Web.

Que se passerait-il si un fournisseur tiers d'une API, de polices Web ou d'un autre service couramment utilisé pouvait déployer son propre service worker chargé de gérer les requêtes envoyées par d'autres origines vers la sienne ? Les fournisseurs peuvent implémenter leur propre logique de mise en réseau personnalisée et utiliser une seule instance de cache faisant autorité pour stocker leurs réponses. Grâce à la récupération externe, ce type de déploiement de service worker tiers est désormais une réalité.

Le déploiement d'un service worker qui implémente une récupération externe est judicieux pour tout fournisseur d'un service auquel on accède via des requêtes HTTPS à partir de navigateurs. Pensez simplement aux scénarios dans lesquels vous pourriez fournir une version indépendante du réseau de votre service, dans laquelle les navigateurs pourraient tirer parti d'un cache de ressources commun. Voici quelques exemples de services qui peuvent en bénéficier:

  • Fournisseurs d'API avec interfaces RESTful
  • Fournisseurs de polices Web
  • Fournisseurs d'analyse
  • Fournisseurs d'hébergement d'images
  • Réseaux de diffusion de contenu génériques

Imaginons, par exemple, que vous soyez un fournisseur d'analyse. En déployant un worker de service de récupération étranger, vous pouvez vous assurer que toutes les requêtes envoyées à votre service qui échouent lorsqu'un utilisateur est hors connexion sont mises en file d'attente et réexécutées une fois la connectivité rétablie. Bien que les clients d'un service puissent implémenter un comportement similaire via des service workers first party, demander à chaque client d'écrire une logique personnalisée pour votre service n'est pas aussi évolutif que de s'appuyer sur un service worker de récupération étranger partagé que vous déployez.

Prérequis

Jeton Origin Trial

La récupération externe est toujours considérée comme expérimentale. Pour éviter d'implémenter cette conception trop tôt avant qu'elle ne soit entièrement spécifiée et acceptée par les fournisseurs de navigateurs, elle a été implémentée dans Chrome 54 en tant que phase d'évaluation. Tant que la récupération externe reste expérimentale, pour utiliser cette nouvelle fonctionnalité avec le service que vous hébergez, vous devez demander un jeton limité à l'origine spécifique de votre service. Le jeton doit être inclus en tant qu'en-tête de réponse HTTP dans toutes les requêtes inter-origines pour les ressources que vous souhaitez gérer via une récupération externe, ainsi que dans la réponse de votre ressource JavaScript de service worker:

Origin-Trial: token_obtained_from_signup

L'essai prendra fin en mars 2017. D'ici là, nous devrions avoir déterminé les modifications nécessaires pour stabiliser la fonctionnalité et (si tout va bien) l'activer par défaut. Si l'extraction externe n'est pas activée par défaut d'ici là, la fonctionnalité associée aux jetons de test Origin existants ne fonctionnera plus.

Pour tester plus facilement la récupération externe avant de vous inscrire pour un jeton de phase d'évaluation Origin, vous pouvez contourner l'exigence dans Chrome pour votre ordinateur local en accédant à chrome://flags/#enable-experimental-web-platform-features et en activant le commutateur "Experimental Web Platform features" (Fonctionnalités expérimentales de la plate-forme Web). Notez que vous devez effectuer cette opération dans chaque instance de Chrome que vous souhaitez utiliser pour vos tests locaux. En revanche, avec un jeton de test Origin, la fonctionnalité sera disponible pour tous vos utilisateurs Chrome.

HTTPS

Comme pour tous les déploiements de service worker, vous devez accéder au serveur Web que vous utilisez pour diffuser vos ressources et votre script de service worker via HTTPS. De plus, l'interception de récupération externe ne s'applique qu'aux requêtes provenant de pages hébergées sur des origines sécurisées. Par conséquent, les clients de votre service doivent utiliser HTTPS pour profiter de votre implémentation de récupération externe.

Utiliser Foreign Fetch

Maintenant que vous avez pris connaissance des conditions préalables, examinons les détails techniques nécessaires pour mettre en service un worker de service de récupération étranger.

Enregistrer votre service worker

Le premier défi auquel vous êtes susceptible de vous heurter est de savoir comment enregistrer votre service worker. Si vous avez déjà travaillé avec des services workers, vous connaissez probablement les points suivants:

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

Ce code JavaScript pour l'enregistrement d'un service worker propriétaire est pertinent dans le contexte d'une application Web, déclenché par un utilisateur qui accède à une URL que vous contrôlez. Toutefois, ce n'est pas une approche viable pour enregistrer un service worker tiers, car le seul navigateur qui interagira avec votre serveur demandera une sous-ressource spécifique, et non une navigation complète. Si le navigateur demande, par exemple, une image à partir d'un serveur CDN que vous gérez, vous ne pouvez pas ajouter cet extrait de code JavaScript au début de votre réponse et vous attendre à ce qu'il soit exécuté. Une autre méthode d'enregistrement du service worker, en dehors du contexte d'exécution JavaScript normal, est requise.

La solution se présente sous la forme d'un en-tête HTTP que votre serveur peut inclure dans n'importe quelle réponse:

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

Décomposons cet exemple d'en-tête en ses composants, chacun étant séparé par un caractère ;.

  • </service-worker.js> est obligatoire et permet de spécifier le chemin d'accès à votre fichier de service worker (remplacez /service-worker.js par le chemin d'accès approprié à votre script). Cela correspond directement à la chaîne scriptURL qui serait transmise en tant que premier paramètre à navigator.serviceWorker.register(). La valeur doit être placée entre des caractères <> (comme l'exige la spécification de l'en-tête Link). Si une URL relative plutôt qu'absolue est fournie, elle sera interprétée comme étant relative à l'emplacement de la réponse.
  • rel="serviceworker" est également obligatoire et doit être inclus sans aucune personnalisation.
  • scope=/ est une déclaration de portée facultative, équivalente à la chaîne options.scope que vous pouvez transmettre en tant que deuxième paramètre à navigator.serviceWorker.register(). Dans de nombreux cas d'utilisation, vous pouvez utiliser la portée par défaut. N'hésitez donc pas à ne pas l'indiquer, sauf si vous en avez besoin. Les mêmes restrictions concernant la portée maximale autorisée, ainsi que la possibilité de les assouplir via l'en-tête Service-Worker-Allowed, s'appliquent aux enregistrements d'en-têtes Link.

Tout comme pour un enregistrement de service worker "traditionnel", l'utilisation de l'en-tête Link permet d'installer un service worker qui sera utilisé pour la prochaine requête effectuée sur le champ d'application enregistré. Le corps de la réponse incluant l'en-tête spécial sera utilisé tel quel et sera immédiatement disponible pour la page, sans attendre que l'installation du service worker externe soit terminée.

N'oubliez pas que la récupération externe est actuellement implémentée en tant que test d'origine. Vous devez donc également inclure un en-tête Origin-Trial valide avec votre en-tête de réponse Link. L'ensemble minimal d'en-têtes de réponse à ajouter pour enregistrer votre worker de service de récupération externe est

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

Enregistrement de débogage

Lors du développement, vous devrez probablement vérifier que votre worker de service de récupération externe est correctement installé et traite les requêtes. Vous pouvez vérifier plusieurs éléments dans les outils pour les développeurs de Chrome pour vous assurer que tout fonctionne comme prévu.

Les en-têtes de réponse appropriés sont-ils envoyés ?

Pour enregistrer le service worker de récupération externe, vous devez définir un en-tête Link sur une réponse à une ressource hébergée sur votre domaine, comme décrit précédemment dans cet article. Pendant la phase d'évaluation de l'origine, et en supposant que vous n'ayez pas défini chrome://flags/#enable-experimental-web-platform-features, vous devez également définir un en-tête de réponse Origin-Trial. Pour vérifier que votre serveur Web définit ces en-têtes, consultez l'entrée dans le panneau "Network" (Réseau) de DevTools:

En-têtes affichés dans le panneau &quot;Réseau&quot;.

Le service worker de récupération externe est-il correctement enregistré ?

Vous pouvez également vérifier l'enregistrement du service worker sous-jacent, y compris son champ d'application, en consultant la liste complète des service workers dans le panneau "Application" de DevTools. Veillez à sélectionner l'option "Tout afficher", car par défaut, seuls les service workers de l'origine actuelle s'affichent.

Nœud de calcul du service de récupération externe dans le panneau &quot;Applications&quot;.

Gestionnaire d'événements d'installation

Maintenant que vous avez enregistré votre service worker tiers, il aura la possibilité de répondre aux événements install et activate, comme n'importe quel autre service worker. Il peut exploiter ces événements pour, par exemple, renseigner les caches avec les ressources requises lors de l'événement install ou supprimer les caches obsolètes lors de l'événement activate.

En plus des activités de mise en cache d'événements install normales, une étape supplémentaire est obligatoire dans le gestionnaire d'événements install de votre service worker tiers. Votre code doit appeler registerForeignFetch(), comme dans l'exemple suivant:

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

Deux options de configuration sont requises:

  • scopes accepte un tableau d'une ou de plusieurs chaînes, chacune représentant un champ d'application pour les requêtes qui déclencheront un événement foreignfetch. Mais attendez, vous vous demandez peut-être : J'ai déjà défini un champ d'application lors de l'enregistrement du service worker ! C'est vrai, et cette portée globale reste pertinente. Chaque portée que vous spécifiez ici doit être égale à la portée globale du service worker ou en être un sous-ensemble. Les restrictions de champ d'application supplémentaires ici vous permettent de déployer un service worker polyvalent capable de gérer à la fois les événements fetch propriétaires (pour les requêtes effectuées à partir de votre propre site) et les événements foreignfetch tiers (pour les requêtes effectuées à partir d'autres domaines). Elles indiquent clairement que seul un sous-ensemble de votre champ d'application plus large doit déclencher foreignfetch. En pratique, si vous déployez un service worker dédié à la gestion uniquement d'événements foreignfetch tiers, vous n'avez qu'à utiliser un seul champ d'application explicite, égal à celui de votre service worker. C'est ce que l'exemple ci-dessus fera, en utilisant la valeur self.registration.scope.
  • origins accepte également un tableau d'une ou de plusieurs chaînes et vous permet de limiter votre gestionnaire foreignfetch à répondre uniquement aux requêtes provenant de domaines spécifiques. Par exemple, si vous autorisez explicitement "https://example.com", une requête envoyée à partir d'une page hébergée sur https://example.com/path/to/page.html pour une ressource diffusée à partir de votre champ d'application de récupération externe déclenchera votre gestionnaire de récupération externe, mais les requêtes envoyées à partir de https://random-domain.com/path/to/page.html ne déclencheront pas votre gestionnaire. Sauf si vous avez une raison spécifique de ne déclencher votre logique de récupération externe que pour un sous-ensemble d'origines distantes, vous pouvez simplement spécifier '*' comme seule valeur du tableau. Toutes les origines seront alors autorisées.

Gestionnaire d'événements foreignfetch

Maintenant que vous avez installé votre service worker tiers et qu'il a été configuré via registerForeignFetch(), il peut intercepter les requêtes de sous-ressources inter-origines envoyées à votre serveur qui relèvent du champ d'application de la récupération étrangère.

Dans un service worker first party traditionnel, chaque requête déclenchait un événement fetch auquel votre service worker pouvait répondre. Notre service worker tiers a la possibilité de gérer un événement légèrement différent, nommé foreignfetch. Conceptuellement, les deux événements sont assez similaires. Ils vous permettent d'inspecter la requête entrante et, éventuellement, de lui répondre 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']
        };
    })
    );
});

Malgré les similitudes conceptuelles, il existe quelques différences pratiques lorsque vous appelez respondWith() sur un ForeignFetchEvent. Au lieu de simplement fournir un Response (ou un Promise qui se résout avec un Response) à respondWith(), comme vous le faites avec un FetchEvent, vous devez transmettre un Promise qui se résout avec un objet avec des propriétés spécifiques à l'respondWith() de ForeignFetchEvent:

  • response est obligatoire et doit être défini sur l'objet Response qui sera renvoyé au client à l'origine de la requête. Si vous fournissez autre chose qu'un Response valide, la requête du client sera arrêtée avec une erreur réseau. Contrairement à l'appel de respondWith() dans un gestionnaire d'événements fetch, vous devez fournir un Response ici, et non un Promise qui se résout avec un Response. Vous pouvez créer votre réponse via une chaîne de promesses et transmettre cette chaîne en tant que paramètre à respondWith() de foreignfetch, mais la chaîne doit se résoudre avec un objet contenant la propriété response définie sur un objet Response. Vous pouvez voir une démonstration de ce point dans l'exemple de code ci-dessus.
  • origin est facultatif. Il permet de déterminer si la réponse renvoyée est opaque ou non. Si vous ne le faites pas, la réponse sera opaque et le client n'aura qu'un accès limité au corps et aux en-têtes de la réponse. Si la requête a été effectuée avec mode: 'cors', le renvoi d'une réponse opaque sera traité comme une erreur. Toutefois, si vous spécifiez une valeur de chaîne égale à l'origine du client distant (qui peut être obtenue via event.origin), vous activez explicitement la fourniture d'une réponse compatible avec le CORS au client.
  • headers est également facultatif et n'est utile que si vous spécifiez également origin et renvoyez une réponse CORS. Par défaut, seuls les en-têtes figurant dans la liste des en-têtes de réponse de la liste d'autorisation CORS sont inclus dans votre réponse. Si vous devez filtrer davantage les éléments renvoyés, les en-têtes acceptent une liste d'un ou de plusieurs noms d'en-tête. Ils l'utiliseront comme liste d'autorisation des en-têtes à exposer dans la réponse. Vous pouvez ainsi activer le CORS tout en empêchant les en-têtes de réponse potentiellement sensibles d'être exposés directement au client distant.

Il est important de noter que lorsque le gestionnaire foreignfetch est exécuté, il a accès à toutes les identifiants et à l'autorité ambiante de l'origine hébergeant le service worker. En tant que développeur déployant un service worker étranger compatible avec la récupération, il est de votre responsabilité de vous assurer que vous ne divulguez aucune donnée de réponse privilégiée qui ne serait pas disponible autrement grâce à ces identifiants. Exiger une activation pour les réponses CORS est une étape permettant de limiter l'exposition involontaire, mais en tant que développeur, vous pouvez envoyer explicitement des requêtes fetch() dans votre gestionnaire foreignfetch qui n'utilisent pas les identifiants implicites 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}))
    );
});

Considérations concernant le client

Certains facteurs supplémentaires affectent la façon dont votre worker de service de récupération externe traite les requêtes envoyées par les clients de votre service.

Clients disposant de leur propre service worker propriétaire

Certains clients de votre service peuvent déjà disposer de leur propre service worker first party, qui gère les requêtes provenant de leur application Web. Qu'est-ce que cela signifie pour votre service worker de récupération tiers étranger ?

Le ou les gestionnaires fetch d'un service worker propriétaire ont la première opportunité de répondre à toutes les requêtes envoyées par l'application Web, même si un service worker tiers avec foreignfetch activé dispose d'un champ d'application couvrant la requête. Toutefois, les clients disposant de service workers first party peuvent toujours profiter de votre service worker de récupération externe.

Dans un service worker propriétaire, l'utilisation de fetch() pour récupérer des ressources multi-origines déclenche le service worker de récupération étranger approprié. Cela signifie qu'un code comme celui-ci peut tirer parti de votre gestionnaire 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));
});

De même, s'il existe des gestionnaires de récupération propriétaires, mais qu'ils n'appellent pas event.respondWith() lors du traitement des requêtes pour votre ressource multi-origine, la requête est automatiquement transmise à votre gestionnaire 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.
});

Si un gestionnaire fetch first party appelle event.respondWith(), mais n'utilise pas fetch() pour demander une ressource dans le champ d'application de votre récupération externe, votre service worker de récupération externe n'aura pas la possibilité de gérer la requête.

Clients qui ne disposent pas de leur propre service worker

Tous les clients qui envoient des requêtes à un service tiers peuvent en bénéficier lorsque le service déploie un service worker de récupération étranger, même s'ils n'utilisent pas encore leur propre service worker. Les clients n'ont rien de spécifique à faire pour activer l'utilisation d'un worker de service de récupération étranger, à condition d'utiliser un navigateur compatible. Cela signifie qu'en déployant un worker de service de récupération étranger, votre logique de requête personnalisée et votre cache partagé bénéficieront immédiatement à de nombreux clients de votre service, sans qu'ils aient à prendre d'autres mesures.

Synthèse: où les clients recherchent-ils une réponse ?

En tenant compte des informations ci-dessus, nous pouvons assembler une hiérarchie de sources qu'un client utilisera pour trouver une réponse à une requête inter-origine.

  1. Gestionnaire fetch d'un service worker propriétaire (le cas échéant)
  2. Le gestionnaire foreignfetch d'un service worker tiers (s'il est présent et uniquement pour les requêtes inter-origines)
  3. Le cache HTTP du navigateur (si une réponse récente existe)
  4. Le réseau

Le navigateur commence par le haut et, en fonction de l'implémentation du service worker, continue à descendre la liste jusqu'à trouver une source de réponse.

En savoir plus

Tenez-vous au courant

L'implémentation de la fonctionnalité Origin Trial de récupération externe dans Chrome est susceptible d'être modifiée en fonction des commentaires des développeurs. Nous mettrons à jour ce post au fur et à mesure des modifications apportées et indiquerons les modifications spécifiques ci-dessous. Nous communiquerons également des informations sur les changements majeurs via le compte Twitter @chromiumdev.