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 effectuées par leurs applications Web. Ils peuvent ainsi continuer à travailler même hors connexion, combattre le lie-fi et implémenter des interactions de cache complexes telles que la méthode stale-while-revalidate. Cependant, les service workers ont toujours été lié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 envoyées par votre application Web. Dans ce modèle, chaque service worker gère même les requêtes multi-origines, par exemple vers une API tierce ou pour les polices Web.

Que se passe-t-il si un fournisseur tiers d'une API, de polices Web ou d'un autre service couramment utilisé avait le pouvoir de déployer son propre service worker capable de traiter les requêtes effectuées par d'autres origines vers leur origine ? Les fournisseurs peuvent implémenter leur propre logique de mise en réseau personnalisée et exploiter une instance de cache unique et fiable pour stocker leurs réponses. Désormais, grâce à la récupération externe, ce type de déploiement de service worker tiers est une réalité.

Le déploiement d'un service worker qui implémente une récupération externe est judicieux pour tout fournisseur de service accessible via des requêtes HTTPS provenant de navigateurs. Pensez simplement aux scénarios dans lesquels vous pourriez fournir une version de service indépendante du réseau, dans laquelle les navigateurs pourraient exploiter un cache de ressources commun. Il peut s'agir, entre autres, des services suivants:

  • Fournisseurs d'API avec des interfaces RESTful
  • Fournisseurs de polices Web
  • Fournisseurs de solutions 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 service worker externe, vous pouvez vous assurer que toutes les requêtes adressées à votre service qui échouent lorsqu'un utilisateur est hors connexion sont mises en file d'attente et relancées une fois la connectivité rétablie. Bien que les clients d'un service puissent mettre en œuvre un comportement similaire via des service workers propriétaires, exiger que chaque client écrive une logique sur mesure pour votre service n'est pas aussi évolutif que de s'appuyer sur un service worker d'extraction externe que vous déployez.

Prérequis

Jeton de la phase d'évaluation

La récupération de données étrangères est toujours considérée comme expérimentale. Afin d'éviter d'intégrer prématurément cette conception avant qu'elle ne soit entièrement spécifiée et approuvée par les fournisseurs de navigateurs, elle a été implémentée dans Chrome 54 en tant qu'évaluation d'origine. Tant que la récupération étrangère 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 pour votre ressource JavaScript de service worker:

Origin-Trial: token_obtained_from_signup

L'essai prendra fin en mars 2017. À ce stade, nous nous attendons à avoir identifié les modifications nécessaires pour stabiliser la fonctionnalité et, nous l'espérons, l'activer par défaut. Si la récupération en externe n'est pas activée par défaut à ce moment-là, la fonctionnalité associée aux jetons de phase d'évaluation existants cessera de fonctionner.

Pour faciliter l'expérimentation de la récupération en externe avant de vous inscrire pour obtenir un jeton officiel pour la phase d'évaluation, vous pouvez contourner cette obligation dans Chrome pour votre ordinateur local en accédant à chrome://flags/#enable-experimental-web-platform-features et en activant l'indicateur "Fonctionnalités expérimentales de la plate-forme Web". Veuillez noter que cette opération doit être effectuée dans chaque instance de Chrome que vous souhaitez utiliser dans le cadre de vos tests locaux. En revanche, avec un jeton de phase d'évaluation, tous vos utilisateurs Chrome ont accès à cette fonctionnalité.

HTTPS

Comme pour tous les déploiements de service worker, le serveur Web que vous utilisez pour diffuser vos ressources et votre script de service worker doit être accessible via HTTPS. En outre, l'interception de récupération en externe ne s'applique qu'aux requêtes provenant de pages hébergées sur des origines sécurisées. Les clients de votre service doivent donc utiliser HTTPS pour tirer parti de votre implémentation de récupération étrangère.

Utiliser la récupération de données étrangères

Une fois les prérequis résolus, examinons les détails techniques nécessaires à la mise en route d'un service worker d'extraction étranger.

Enregistrer votre service worker

La première difficulté que vous risquez de rencontrer est de savoir comment enregistrer un service worker. Si vous avez déjà travaillé avec des service 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 utile dans le contexte d'une application Web, lorsqu'un utilisateur accède à une URL que vous contrôlez. Toutefois, il ne s'agit pas d'une approche viable pour enregistrer un service worker tiers, lorsque la seule interaction du navigateur avec votre serveur demande une sous-ressource spécifique, et non une navigation complète. Si le navigateur demande, par exemple, une image d'un serveur CDN que vous gérez, vous ne pouvez pas ajouter cet extrait de code JavaScript à votre réponse et vous attendre à ce qu'il soit exécuté. Une méthode d'enregistrement du service worker différente, en dehors du contexte d'exécution JavaScript normal, est requise.

Cette 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, séparés par un caractère ;.

  • </service-worker.js> est requis. Il 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 sinon transmise en tant que premier paramètre à navigator.serviceWorker.register(). La valeur doit être entourée de <> caractères (conformément à la spécification de l'en-tête Link). Si une URL relative, et non 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 champ d'application 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, le champ d'application par défaut peut être utilisé. Vous pouvez donc l'omettre, sauf si vous en avez besoin. Les mêmes restrictions concernant le champ d'application maximal autorisé, ainsi que la possibilité d'assouplir ces restrictions via l'en-tête Service-Worker-Allowed, s'appliquent aux enregistrements d'en-tête Link.

Comme pour l'enregistrement d'un service worker "traditionnel", l'en-tête Link permet d'installer un service worker qui sera utilisé pour la requête prochaine effectuée sur le champ d'application enregistré. Le corps de la réponse qui inclut l'en-tête spécial est utilisé tel quel. Il est disponible sur la page immédiatement, sans attendre que le travailleur étranger termine l'installation.

N'oubliez pas que la récupération en externe est actuellement mise en œuvre en tant que phase d'évaluation. Par conséquent, vous devez inclure un en-tête Origin-Trial valide en plus de l'en-tête de la réponse de lien. L'ensemble minimal d'en-têtes de réponse à ajouter pour enregistrer votre service worker d'extraction externe est

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

Débogage de l'enregistrement

Pendant le développement, vous souhaiterez probablement vérifier que votre service worker externe d'extraction est correctement installé et traite les requêtes. Vous pouvez vérifier certains points dans les outils pour les développeurs de Chrome afin de 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 d'extraction externe, vous devez définir un en-tête "Link" (Lien) dans une réponse à une ressource hébergée sur votre domaine, comme décrit précédemment dans cet article. Pendant la période d'évaluation, et en supposant que vous n'avez pas défini chrome://flags/#enable-experimental-web-platform-features, vous devez également définir un en-tête de réponse Origin-Trial. Vous pouvez vérifier que votre serveur Web définit ces en-têtes en consultant l'entrée du panneau "Network" (Réseau) des outils de développement:

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

Le service worker de récupération de données étrangères 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 des outils de développement. Veillez à sélectionner l'option "Tout afficher", car par défaut, seuls les service workers s'affichent pour l'origine actuelle.

Le service worker externe de récupération des données 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 élaguer les caches obsolètes dans l'événement activate.

Au-delà des activités normales de mise en cache des événements install, 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 disponibles:

  • 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, vous pensez peut-être : J'ai déjà défini un champ d'application lors de l'enregistrement du service worker. C'est vrai, et ce champ d'application global reste pertinent. Chaque champ d'application que vous spécifiez ici doit être égal ou inférieur au champ d'application global du service worker. Les restrictions supplémentaires liées au champ d'application vous permettent de déployer un service worker polyvalent qui peut 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). Ils indiquent également que seul un sous-ensemble de votre champ d'application global doit déclencher foreignfetch. En pratique, si vous déployez un service worker dédié à la gestion uniquement des événements foreignfetch tiers, il vous suffit d'utiliser un champ d'application unique et explicite correspondant au champ d'application global de votre service worker. C'est ce que fera l'exemple ci-dessus, en utilisant la valeur self.registration.scope.
  • origins accepte également un tableau d'une ou plusieurs chaînes et vous permet de limiter votre gestionnaire foreignfetch pour qu'il ne réponde qu'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 depuis un champ d'application d'extraction étranger déclenchera votre gestionnaire d'extraction étranger, mais les requêtes effectuées depuis https://random-domain.com/path/to/page.html ne déclencheront pas votre gestionnaire. Sauf si vous avez une raison spécifique de déclencher votre logique de récupération étrangère pour un sous-ensemble d'origines distantes, il vous suffit de spécifier '*' comme seule valeur du tableau. Toutes les origines seront alors autorisées.

Gestionnaire d'événements externalfetch

Maintenant que vous avez installé votre service worker tiers et qu'il est configuré via registerForeignFetch(), il pourra intercepter les requêtes de sous-ressources multi-origines adressées à votre serveur qui sont comprises dans le champ d'application de récupération externe.

Dans un service worker traditionnel, chaque requête déclencherait un événement fetch auquel votre service worker avait pu 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 dans la pratique quelques différences lorsque vous appelez respondWith() sur un ForeignFetchEvent. Au lieu de simplement fournir un Response (ou un Promise qui se résout avec une Response) vers respondWith(), comme vous le faites avec une FetchEvent, vous devez transmettre un Promise qui se résout avec un objet avec des propriétés spécifiques sur le 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 interrompue et une erreur réseau s'affichera. Contrairement à l'appel de respondWith() dans un gestionnaire d'événements fetch, vous devez fournir une Response ici, et non une Promise qui se résout par une Response. Vous pouvez construire votre réponse via une chaîne de promesse et transmettre cette chaîne en tant que paramètre au respondWith() de foreignfetch. Toutefois, la chaîne doit se résoudre avec un objet contenant la propriété response définie sur un objet Response. L'exemple de code ci-dessus illustre ce processus.
  • origin est facultatif et permet de déterminer si la réponse renvoyée est opaque. Si vous l'omettez, la réponse sera opaque et le client aura 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 considéré 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 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 des en-têtes de réponse ajoutés à la liste sécurisée CORS sont inclus dans votre réponse. Si vous devez filtrer davantage les éléments renvoyés, les en-têtes prennent une liste d'un ou de plusieurs noms d'en-tête et l'utilisent comme liste d'autorisation des en-têtes à exposer dans la réponse. Cela vous permet d'activer CORS tout en empêchant l'exposition directe des en-têtes de réponse potentiellement sensibles 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 qui héberge le service worker. En tant que développeur déployant un service worker permettant une extraction étrangère, il est de votre responsabilité de vous assurer que vous ne divulguez pas de données de réponse privilégiées qui ne seraient autrement disponibles grâce à ces informations d'identification. Demander une activation pour les réponses CORS est une étape permettant de limiter l'exposition accidentelle, mais en tant que développeur, vous pouvez explicitement envoyer 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

D'autres considérations affectent la manière dont votre service de récupération étranger traite les requêtes des clients de votre service.

Clients ayant leur propre service worker propriétaire

Certains clients de votre service ont peut-être déjà un service worker propriétaire qui traite les demandes provenant de leur application Web. Qu'est-ce que cela signifie pour votre service de récupération étranger tiers ?

Le ou les gestionnaires fetch d'un service worker propriétaire ont la première occasion de répondre à toutes les requêtes effectuées par l'application Web, même si un service worker tiers pour lequel foreignfetch est activé et dont le champ d'application couvre la requête. Toutefois, les clients ayant recours à des services d'extraction peuvent tout de même profiter des services d'extraction étranger !

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 externe approprié. Cela signifie qu'un code tel que 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 passera automatiquement par 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 le champ d'application d'extraction externe, votre service worker d'extraction étranger ne pourra pas 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 bénéficier du déploiement d'un service worker d'extraction à l'étranger, même s'ils n'utilisent pas déjà leur propre service worker. Aucune action spécifique n'est requise de la part des clients pour accepter l'utilisation d'un service worker étranger, à condition qu'ils utilisent un navigateur compatible. Cela signifie qu'en déployant un service worker externe, 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 à intervenir.

Tout mettre en place: où les clients recherchent 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 multi-origine.

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

Le navigateur part du haut de la page et, selon l'implémentation du service worker, continue de parcourir la liste jusqu'à ce qu'il trouve une source pour la réponse.

En savoir plus

Tenez-vous informé

La mise en œuvre dans Chrome de la phase d'évaluation de récupération en externe est susceptible d'être modifiée en fonction des commentaires des développeurs. Nous allons maintenir cet article à jour en effectuant des modifications intégrées, et les modifications spécifiques seront mentionnées ci-dessous au fur et à mesure. Nous partagerons également des informations sur les changements majeurs via le compte Twitter @chromiumdev.