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 "lie-fi" 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 disposant d'interfaces RESTful
  • Fournisseurs de polices Web
  • Fournisseurs d'analyse
  • Fournisseurs d'hébergement d'images
  • Réseaux de diffusion de contenu génériques

Imaginez, par exemple, que vous soyez un fournisseur de solutions 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. Afin d'é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 de données étrangères reste expérimentale, pour utiliser cette nouvelle fonctionnalité avec le service que vous hébergez, vous devrez 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 multi-origines pour les ressources que vous souhaitez gérer via une récupération étrangère, 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 la récupération 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 de l'origine officiel, 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 dans 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.

Utilisation de la récupération des données à l'étranger

Une fois les conditions préalables remplies, examinons les détails techniques nécessaires à la mise en service d'un service worker de récupération étrangère.

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 service workers, vous connaissez probablement les concepts 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 entourée de caractères <> (conformément à la spécification d'en-tête Link). Si une URL relative est indiquée, et non absolue, elle sera interprétée comme par rapport à 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 à l'ignorer, sauf si vous savez que vous en avez besoin. Les mêmes restrictions concernant le champ d'application maximal autorisé, ainsi que la possibilité de les assouplir via l'en-tête Service-Worker-Allowed, s'appliquent aux enregistrements d'en-tête 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

Déboguer l'enregistrement

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 étranger, 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 correspondante dans le panneau Network (Réseau) de la page "DevTools" (Outils de développement) :

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

Le service worker de récupération des données à l'étranger 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", puisque par défaut, vous ne verrez que les service workers pour l'origine actuelle.

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 peut répondre aux événements install et activate, comme le ferait n'importe quel autre service worker. Il peut exploiter ces événements pour, par exemple, remplir les caches avec les ressources requises lors de l'événement install, ou éliminer les caches obsolètes dans 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éclenchent 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é uniquement à la gestion des événements tiers de type foreignfetch, il vous suffit d'utiliser un seul champ d'application explicite et égal au champ d'application global 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 effectué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 d'extraction étrangère déclenchera votre gestionnaire d'extraction étrangère, mais pas les requêtes effectuées depuis https://random-domain.com/path/to/page.html. 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.

Le gestionnaire d'événements externalfetch

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 de fournir éventuellement une réponse 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 et 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 de la liste d'en-têtes de réponse ajoutés à la liste d'autorisation CORS seront 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 plusieurs noms d'en-têtes, qu'ils utilisent 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 à tous 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}))
    );
});

Remarques 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 disposent peut-être déjà de leur propre service worker propriétaire qui gère les requêtes provenant de leur application Web. Qu'est-ce que cela signifie pour votre service worker 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 exploiter 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 propriétaire appelle event.respondWith(), mais n'utilise pas fetch() pour demander une ressource dans votre champ d'application de récupération étrangère, votre nœud de calcul de service de récupération des données étrangères n'aura pas le temps de traiter la requête.

Clients ne disposant 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 des sources qu'un client utilisera pour trouver une réponse à une requête multi-origine.

  1. Gestionnaire fetch d'un service worker propriétaire (le cas échéant)
  2. Gestionnaire foreignfetch d'un service worker tiers (le cas échéant, et uniquement pour les requêtes multi-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, descend dans 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.