Skrypty service worker z innych domen – eksperymenty z pobieraniem z innych domen

Tło

Usługa w tle umożliwia deweloperom stron internetowych reagowanie na żądania sieciowe wysyłane przez ich aplikacje internetowe, co pozwala im pracować nawet w trybie offline, zwalczać problemy z siecią LTE („lie-fi”) oraz wdrażać złożone interakcje z pamięcią podręcznej, takie jak sprawdzanie ważności w trybie „stale-while-revalidate”. Jednak skrypty service worker były do tej pory powiązane z konkretnym źródłem. Jako właściciel aplikacji internetowej musisz napisać i wdrażać skrypt service worker, który przechwytuje wszystkie żądania sieciowe wysyłane przez Twoją aplikację internetową. W tym modelu każdy pracownik usługi odpowiada za obsługę żądań między domenami, na przykład do interfejsu API innej firmy lub do czcionek internetowych.

Co by się stało, gdyby zewnętrzny dostawca interfejsu API, czcionek internetowych lub innej powszechnie używanej usługi mógł wdrożyć własnego pracownika usługi, który mógłby obsługiwać żądania wysyłane przez inne źródła do ich źródła? Dostawcy mogliby stosować własną niestandardową logikę sieciową i korzystać z jednego wiarygodnego elementu pamięci podręcznej do przechowywania odpowiedzi. Teraz, dzięki zapytaniu zewnętrznemu, tego typu wdrażanie usług innych firm jest możliwe.

Wdrożenie usługi roboczej, która implementuje pobieranie z zewnętrznego źródła, ma sens w przypadku każdego dostawcy usługi, do której dostęp uzyskuje się za pomocą żądań HTTPS z przeglądarek. Wystarczy pomyśleć o scenariuszach, w których można udostępnić wersję usługi niezależną od sieci, w której przeglądarki mogą korzystać z wspólnej pamięci podręcznej zasobów. Usługi, które mogą korzystać z tych funkcji, to m.in.:

  • dostawcy interfejsów RESTful,
  • Dostawcy czcionek internetowych
  • Dostawcy usług analitycznych
  • Dostawcy usług hostingu obrazów
  • Ogólne sieci dostarczania treści

Wyobraź sobie np., że jesteś dostawcą usług analitycznych. Dzięki wdrożeniu usługi pobierania w obcym procesie możesz mieć pewność, że wszystkie żądania wysyłane do usługi, które nie powiodły się, gdy użytkownik był offline, zostaną umieszczone w kolejce i ponownie odtworzone, gdy połączenie zostanie przywrócone. Chociaż klienci usługi mogą wdrażać podobne zachowania za pomocą własnych service workerów, wymaganie od każdego klienta napisania niestandardowej logiki dla usługi nie jest tak elastyczne jak korzystanie z wspólnego, zewnętrznego service workera do pobierania, który wdrażasz.

Wymagania wstępne

Token wersji próbnej Origin

Funkcja pobierania z innego źródła jest nadal eksperymentalna. Aby uniknąć przedwczesnego wdrożenia tej funkcji, zanim zostanie ona w pełni określona i zaakceptowana przez dostawców przeglądarek, została ona zaimplementowana w Chrome 54 jako próbna implementacja Origin. Dopóki funkcja pobierania z zewnętrznego źródła jest nadal w fazie eksperymentalnej, aby korzystać z niej w usłudze, którą hostujesz, musisz poprosić o token ograniczony do konkretnego źródła usługi. Token powinien być dołączany jako nagłówek odpowiedzi HTTP we wszystkich żądaniach między domenami dotyczących zasobów, które chcesz obsługiwać za pomocą zewnętrznego pobierania, a także w odpowiedzi na zasób JavaScriptu w usługach workera:

Origin-Trial: token_obtained_from_signup

Okres próbny zakończy się w marcu 2017 r. Do tego czasu spodziewamy się, że uda nam się wprowadzić wszystkie zmiany niezbędne do stabilizacji tej funkcji i (mamy nadzieję) włączymy ją domyślnie. Jeśli do tego czasu nie włączysz pobierania z zewnętrznych źródeł, funkcje powiązane z dotychczasowymi tokenami Origin Trial przestaną działać.

Aby ułatwić eksperymentowanie z obsługą zewnętrznych danych przed zarejestrowaniem się w oficjalnym programie testów Origin Trial, możesz pominąć ten wymóg w Chrome na komputerze lokalnym. Aby to zrobić, otwórz chrome://flags/#enable-experimental-web-platform-features i włącz flagę „Experimental Web Platform features”. Pamiętaj, że musisz to zrobić w przypadku każdej instancji Chrome, której chcesz użyć w eksperymentach lokalnych. W przypadku tokena Origin Trial funkcja będzie dostępna dla wszystkich użytkowników Chrome.

HTTPS

Podobnie jak w przypadku wszystkich usług wdrożeń, serwer internetowy, który służy do obsługi zasobów i skryptu usługi musi być dostępny przez HTTPS. Ponadto przechwytywanie pobierania z innych źródeł dotyczy tylko żądań pochodzących ze stron hostowanych w bezpiecznych źródłach, więc klienci Twojej usługi muszą używać HTTPS, aby korzystać z implementacji pobierania z innych źródeł.

Używanie funkcji pobierania z zewnętrznego źródła

Po zapoznaniu się z wymaganiami wstępnymi przyjrzyjmy się szczegółom technicznym, które są potrzebne do uruchomienia usługi pobierania z zewnętrznego źródła.

Rejestrowanie skryptu service worker

Pierwszym problemem, z którym się prawdopodobnie zetkniesz, będzie rejestracja pracownika usługi. Jeśli korzystasz z usług, prawdopodobnie wiesz, że:

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

Ten kod JavaScriptu służący do rejestracji własnego serwisu workera ma sens w kontekście aplikacji internetowej, która jest wywoływana przez użytkownika po przejściu na adres URL kontrolowany przez Ciebie. Nie jest to jednak odpowiednie podejście do rejestrowania usługi workera w usłudze zewnętrznej, gdy jedyną interakcją przeglądarki z serwerem jest żądanie określonego podzasobu, a nie pełna nawigacja. Jeśli przeglądarka prosi o obraz z serwera CDN, którego jesteś właścicielem, nie możesz dodać do odpowiedzi tego fragmentu kodu JavaScript i spodziewać się, że zostanie on wykonany. Wymaga to innej metody rejestracji usługi niż w zwykłym kontekście wykonywania JavaScriptu.

Rozwiązanie polega na dodaniu do odpowiedzi nagłówka HTTP, który serwer może zawierać w dowolnej odpowiedzi:

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

Rozłóżmy ten przykładowy nagłówek na jego komponenty, z których każdy jest oddzielony znakiem ;.

  • Argument </service-worker.js> jest wymagany i służy do określenia ścieżki do pliku usługi (zastąp /service-worker.js odpowiednią ścieżką do skryptu). Odpowiada ono bezpośrednio ciągu znaków scriptURL, który w przeciwnym razie zostałby przekazany jako pierwszy parametr funkcji navigator.serviceWorker.register(). Wartość musi być ujęta w znaki <> (zgodnie z specyfikacją nagłówka Link), a jeśli podany jest adres URL względny, a nie bezwzględny, zostanie on zinterpretowany jako względny względem lokalizacji odpowiedzi.
  • rel="serviceworker" jest też wymagany i powinien być uwzględniony bez konieczności dostosowania.
  • scope=/ to opcjonalna deklaracja zakresu, która jest odpowiednikiem ciągu znaków options.scope, który możesz przekazać jako drugi parametr funkcji navigator.serviceWorker.register(). W wielu przypadkach wystarczy domyślny zakres, więc możesz go pominąć, chyba że wiesz, że go potrzebujesz. Rejestracje nagłówków Link podlegają tym samym ograniczeniom dotyczącym maksymalnego zakresu dozwolonego oraz możliwości złagodzenia tych ograniczeń za pomocą nagłówka Service-Worker-Allowed.

Podobnie jak w przypadku „tradycyjnej” rejestracji pracownika usługi, użycie nagłówka Link spowoduje zainstalowanie pracownika usługi, który będzie używany do następnego żądania wysłanego do zarejestrowanego zakresu. Treść odpowiedzi, która zawiera specjalny nagłówek, zostanie użyta bez zmian i będzie dostępna dla strony natychmiast, bez oczekiwania na zakończenie instalacji przez zewnętrznego pracownika usługi.

Pamiętaj, że pobieranie z innego źródła jest obecnie implementowane jako testowanie origin, więc oprócz nagłówka odpowiedzi Link musisz też umieścić prawidłowy nagłówek Origin-Trial. Minimalny zestaw nagłówków odpowiedzi, które należy dodać, aby zarejestrować element worker usługi pobierania z zewnątrz, to

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

Debugowanie rejestracji

Podczas tworzenia aplikacji warto sprawdzić, czy usługa pobierania z zewnętrznego źródła jest prawidłowo zainstalowana i przetwarza żądania. Aby sprawdzić, czy wszystko działa zgodnie z oczekiwaniami, możesz sprawdzić kilka rzeczy w Narzędziach deweloperskich w Chrome.

Czy wysyłane są prawidłowe nagłówki odpowiedzi?

Aby zarejestrować zewnętrznego pracownika usługi pobierania, musisz ustawić nagłówek Link w odpowiedzi na zasób hostowany w Twojej domenie, tak jak opisano wcześniej w tym poście. W okresie testowania origin, jeśli nie masz ustawionego nagłówka chrome://flags/#enable-experimental-web-platform-features, musisz też ustawić nagłówek odpowiedzi Origin-Trial. Aby sprawdzić, czy Twój serwer WWW ustawia te nagłówki, sprawdź wpis w panelu Sieć w Narzędziach deweloperskich:

Nagłówki wyświetlane w panelu Sieć.

Czy skrypt service worker pobierania z zewnętrznego źródła jest prawidłowo zarejestrowany?

Możesz też sprawdzić podstawową rejestrację usługi workera, w tym jej zakres, przeglądając pełną listę usług workera w panelu aplikacji w DevTools. Pamiętaj, aby wybrać opcję „Pokaż wszystkie”, ponieważ domyślnie zobaczysz tylko service workery dla bieżącego źródła.

Wątek usługi pobierania zewnętrznej w panelu Aplikacje.

Moduł obsługi zdarzenia instalacji

Po zarejestrowaniu workera usługi pochodzącego od firmy zewnętrznej będzie on mógł reagować na zdarzenia installactivate tak samo jak każdy inny worker usługi. Może ona korzystać z tych zdarzeń, aby na przykład wypełniać pamięci podręczne wymaganymi zasobami podczas zdarzenia install lub usuwać nieaktualne pamięci podręczne podczas zdarzenia activate.

Oprócz standardowych działań związanych z przechowywaniem w pamięci podręcznej zdarzeń install jest jeszcze wymagany dodatkowy krok w obsługującym zdarzenie install obsługi usługi w ramach usługi w tle innej firmy. Twój kod musi wywołać funkcję registerForeignFetch(), jak w tym przykładzie:

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

Dostępne są 2 opcje konfiguracji, obie są wymagane:

  • Funkcja scopes przyjmuje tablicę co najmniej 1 ciągu tekstowego, z których każdy reprezentuje zakres żądań, które będą uruchamiać zdarzenie foreignfetch. Ale zaczekaj, powiesz sobie pewnie, że zakres został już zdefiniowany podczas rejestracji skryptu service worker. Zgadza się. Ten ogólny zakres jest nadal istotny. Każdy zakres, który tutaj określisz, musi być równy zakresowi ogólnemu lub być podzbiorem zakresu ogólnego usługi. Dodatkowe ograniczenia zakresu umożliwiają wdrożenie uniwersalnego serwisu workera, który może obsługiwać zarówno zdarzenia własne fetch (w przypadku żądań wysyłanych z Twojej witryny), jak i zdarzenia zewnętrzne foreignfetch (w przypadku żądań wysyłanych z innych domen), oraz określenie, że tylko podzbiór większego zakresu powinien wywoływać foreignfetch. W praktyce, jeśli wdrażasz pracownika serwisowego przeznaczonego tylko do obsługi zdarzeń foreignfetch innych firm, użyj jednego, wyraźnego zakresu, który jest równy ogólnemu zakresowi pracownika serwisowego. W przykładzie powyżej wartość self.registration.scope jest używana do tego celu.
  • Funkcja origins przyjmuje też tablicę zawierającą co najmniej 1 ciąg znaków i umożliwia ograniczenie działania modułu obsługi foreignfetch tylko do odpowiadania na żądania z określonych domen. Jeśli na przykład zezwolisz wyraźnie na „https://example.com”, żądanie wysłane ze strony hostowanej pod adresem https://example.com/path/to/page.html w przypadku zasobu wyświetlanego z obrębu pobierania z zewnętrznego źródła spowoduje uruchomienie modułu obsługi pobierania z zewnętrznego źródła, ale żądania wysłane z adresu https://random-domain.com/path/to/page.html nie uruchomią tego modułu. Jeśli nie masz konkretnego powodu, aby logikę pobierania z zewnętrznego źródła aktywować tylko w przypadku podzbioru źródeł zewnętrznych, możesz jako jedyną wartość w tablicy podać '*'. W ten sposób wszystkie źródła będą dozwolone.

Moduł obsługi zdarzenia foreignfetch

Teraz, gdy masz zainstalowanego zewnętrznego pracownika usługi i został on skonfigurowany za pomocą registerForeignFetch(), może przechwytywać żądania podzasobów z innych źródeł wysyłane do Twojego serwera, które mieszczą się w zakresie pobierania z zewnętrznych źródeł.

W tradycyjnym, własnym komponencie usługi każde żądanie powodowałoby wywołanie zdarzenia fetch, na które komponent usługi mógłby odpowiedzieć. Nasz zewnętrzny pracownik usługi ma możliwość obsłużenia nieco innego zdarzenia o nazwie foreignfetch. Oba te zdarzenia są dość podobne i dają Ci możliwość sprawdzenia przychodzącego żądania oraz opcjonalnie udzielenia odpowiedzi za pomocą 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']
        };
    })
    );
});

Mimo podobieństw koncepcyjnych w praktyce występują pewne różnice w przypadku wywołania respondWith() w ramach ForeignFetchEvent. Zamiast przekazywania respondWith() wartości Response (lub Promise, która jest mapowana na Response) tak jak w przypadku FetchEvent, musisz przekazać Promise, które jest mapowane na obiekt z określonymi właściwościami do respondWith() ForeignFetchEvent:

Pamiętaj, że gdy moduł obsługi foreignfetch jest uruchamiany, ma on dostęp do wszystkich danych uwierzytelniających i środowiskowych uprawnień pochodzenia, które obsługuje moduł service worker. Jako deweloper wdrażający element service worker z obsługą funkcji fetch w obrębie innego podmiotu, masz obowiązek zadbać o to, aby nie doszło do wycieku żadnych poufnych danych odpowiedzi, które nie byłyby dostępne na podstawie tych danych logowania. Wymaganie wyrażenia zgody na odpowiedzi CORS to jeden ze sposobów na ograniczenie przypadkowego ujawnienia danych. Jako deweloper możesz jednak w ramach swojego foreignfetch-handlera wyraźnie wysyłać fetch() żądania, które nie używają domyślnych poświadczeń:

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

Informacje dla klienta

Istnieją pewne dodatkowe kwestie, które wpływają na sposób, w jaki usługa pobierania danych z zewnętrznego serwera obsługuje żądania wysyłane przez klientów usługi.

Klienci, którzy mają własnego pracownika usługi z dostawcy zewnętrznego

Niektórzy klienci Twojej usługi mogą już mieć własne komponenty service worker po stronie klienta, które obsługują żądania pochodzące z ich aplikacji internetowych. Co to oznacza dla komponentu service worker po stronie obcej, który pobiera dane z usługi innej firmy?

Obsługa fetch w usługach własnych workera ma pierwszeństwo w reagowaniu na wszystkie żądania wysyłane przez aplikację internetową, nawet jeśli istnieje usługa zewnętrzna workera z obsługą foreignfetch, której zakres obejmuje żądanie. Klienci z własnymi pracownikami usługi mogą jednak nadal korzystać z pracownika usługi pobierania z innego źródła.

W skrypcie service worker własnym użycie funkcji fetch() do pobierania zasobów z innych źródeł spowoduje wywołanie odpowiedniego skryptu service worker do pobierania z innych źródeł. Oznacza to, że kod podobny do tego może korzystać z obsługi 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));
});

Podobnie, jeśli istnieją własne moduły obsługi pobierania, ale nie wywołują one funkcji event.respondWith() podczas obsługi żądań zasobów z innych źródeł, żądanie zostanie automatycznie przekazane do modułu obsługi 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.
});

Jeśli fetch handler firmy zewnętrznej wywołuje event.respondWith(), ale nie używa fetch(), aby zażądać zasobu w ramach zakresu pobierania z zewnętrznego źródła, skrypt service worker do pobierania z zewnętrznego źródła nie będzie miał możliwości obsłużenia żądania.

Klienci, którzy nie mają własnego serwisu

Wszyscy klienci wysyłający żądania do usługi zewnętrznej mogą korzystać z usług zewnętrznego pobierania, nawet jeśli nie korzystają jeszcze z własnego service workera. Klienci nie muszą nic robić, aby zacząć korzystać z obsługiwanego przez zewnętrzny serwer procesu pobierania, o ile używają przeglądarki, która go obsługuje. Oznacza to, że po wdrożeniu zewnętrznego pracownika usługi pobierania Twoja niestandardowa logika żądania i współdzielona pamięć podręczna będą od razu dostępne dla wielu klientów usługi bez konieczności podejmowania przez nich dodatkowych działań.

Podsumowanie: gdzie klienci szukają odpowiedzi

Biorąc pod uwagę powyższe informacje, możemy utworzyć hierarchię źródeł, których klient będzie używać do znajdowania odpowiedzi na żądanie między domenami.

  1. fetch handler (jeśli występuje) skryptu service worker firmy zewnętrznej
  2. foreignfetchhandler usługi zewnętrznej (jeśli jest obecny i tylko w przypadku żądań między domenami)
  3. Pamięć podręczna HTTP przeglądarki (jeśli istnieje nowa odpowiedź)
  4. Sieć

Przeglądarka zaczyna od góry i w zależności od implementacji usługi roboczej będzie kontynuować przeglądanie listy, aż znajdzie źródło odpowiedzi.

Więcej informacji

Bądź na bieżąco

Implementacja testowania origin w Chrome w przypadku zewnętrznych operacji pobierania może ulec zmianie w odpowiedzi na opinie programistów. Będziemy aktualizować ten post, wprowadzając zmiany w tekście, a także będziemy oznaczać konkretne zmiany w sekcji poniżej w miarę ich wprowadzania. Informacje o istotnych zmianach będziemy też udostępniać na koncie @chromiumdev na Twitterze.