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 serwis worker 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 skryptu service worker, który implementuje pobieranie obce, ma sens w przypadku każdego dostawcy usługi, do którego dostęp jest uzyskiwany za pomocą żądań HTTPS z przeglądarek. Zastanów się tylko, czy możesz udostępnić wersję usługi niezależną od sieci, w której przeglądarki mogłyby skorzystać ze wspólnej pamięci podręcznej zasobów. Usługi, które mogą korzystać z tych funkcji, to m.in.:
- Dostawcy interfejsów API z interfejsami RESTful
- Dostawcy czcionek internetowych
- Dostawcy usług analitycznych
- Dostawcy usług hostingu obrazów
- Ogólne sieci dostarczania treści
Wyobraź sobie na przykład, że jesteś dostawcą usług analitycznych. Wdrażając obcy mechanizm roboczy usługi pobierania, możesz zapewnić, że wszystkie żądania wysyłane do Twojej usługi, które kończyć się niepowodzeniem, gdy użytkownik jest offline, będą w kolejce i odtwarzane ponownie po powrocie połączenia. Mimo że klienty danej usługi implementują podobne działanie za pomocą własnych mechanizmów Service Worker, wymaganie od każdego klienta napisania własnej logiki dla usługi nie jest tak skalowalne, jak korzystanie ze współużytkowanego obcego skryptu usługi pobierania, który wdrożysz.
Wymagania wstępne
Token wersji próbnej Origin
Pobieranie zagranicznych treści jest nadal uznawane za eksperymentalne. 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 należy podawać jako nagłówek odpowiedzi HTTP we wszystkich żądaniach z innych domen dotyczących zasobów, które mają być obsługiwane za pomocą obcego pobierania, oraz w odpowiedzi dla zasobu JavaScript mechanizmu Service Worker:
Origin-Trial: token_obtained_from_signup
Okres próbny kończy się w marcu 2017 r. Spodziewamy się, że wprowadzimy zmiany niezbędne do ustabilizowania funkcji i (miamy nadzieję) włączenia jej domyślnie. Jeśli pobieranie obcych nie będzie do tego czasu domyślnie włączone, funkcje powiązane z istniejącymi tokenami wersji próbnej origin przestaną działać.
Aby ułatwić eksperymentowanie z obsługą zewnętrznych danych przed zarejestrowaniem się w oficjalnym programie Origin Trial, możesz pominąć ten wymóg w Chrome na komputerze lokalnym. W tym celu otwórz chrome://flags/#enable-experimental-web-platform-features
i włącz flagę „Experimental Web Platform features” (Eksperymentalne funkcje platformy internetowej). Pamiętaj, że trzeba to zrobić w każdym wystąpieniu Chrome, którego chcesz używać w lokalnych eksperymentach. Z kolei w ramach wersji próbnej origin token będzie dostępny 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ł.
Korzystanie z obsługi zewnętrznej
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, gdy ten przechodzi do kontrolowanego przez Ciebie adresu URL. Nie jest to jednak odpowiednie podejście do rejestrowania usługi workera u usługodawcy zewnętrznego, gdy jedyną interakcją przeglądarki z serwerem jest żądanie określonego podzasobu, a nie pełna nawigacja. Jeśli przeglądarka żąda np. obrazu 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 ramach zwykłego kontekstu 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="/"
Podzielmy 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 to bezpośrednio ciągowi znakówscriptURL
, który w innym przypadku byłby przekazywany jako pierwszy parametr funkcjinavigator.serviceWorker.register()
. Wartość musi być ujęta w znaki<>
(zgodnie z specyfikacją nagłówkaLink
), 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 odpowiadający ciągowi znakówoptions.scope
, który możesz przekazać jako drugi parametr do funkcjinavigator.serviceWorker.register()
. W wielu przypadkach domyślny zakres jest prawidłowy, więc możesz go pominąć, jeśli nie wiesz, że jest Ci potrzebny. Rejestracje nagłówkówLink
podlegają tym samym ograniczeniom dotyczącym maksymalnego zakresu dozwolonego oraz możliwości złagodzenia tych ograniczeń za pomocą nagłówkaService-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 wdrażane w ramach testowania 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 próbnym origin i zakładając, że nie masz ustawionej 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:
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.
Moduł obsługi zdarzenia instalacji
Po zarejestrowaniu zewnętrznego skryptu service worker będzie można reagować na zdarzenia install
i activate
, tak jak w przypadku każdego innego skryptu service worker. 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
modułu usługi w ramach usługi wtyczki internetowej. 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:
- Funkcja
scopes
przyjmuje tablicę co najmniej 1 ciągu tekstowego, z których każdy reprezentuje zakres żądań, które będą uruchamiać zdarzenieforeignfetch
. Ale zaczekaj, może myślisz, ż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 ogólnemu zakresowi lub być podzbiorem ogólnego zakresu usługi. Dodatkowe ograniczenia zakresu, które znajdziesz tutaj, pozwalają wdrożyć uniwersalny mechanizm Service Worker, który obsługuje zarówno własne zdarzeniafetch
(w przypadku żądań wysłanych z Twojej witryny), jak i zdarzeńforeignfetch
innych firm (w przypadku żądań z innych domen). Dzięki temu masz jasne, że tylko podzbiór większego zakresu może aktywować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. Aby to zrobić, użyj wartościself.registration.scope
w przykładzie powyżej. - Funkcja
origins
przyjmuje też tablicę zawierającą co najmniej 1 ciąg znaków i umożliwia ograniczenie działania modułu obsługiforeignfetch
tylko do odpowiadania na żądania z określonych domen. Jeśli na przykład zezwolisz wyraźnie na dostęp do adresu „https://example.com”, żądanie wysłane ze strony hostowanej pod adresemhttps://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 adresuhttps://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 w prost podać wartość'*'
jako jedyną wartość w tablicy, a 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 środowisku service worker każde żądanie wywołuje zdarzenie fetch
, na które miał on 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ływania 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
:
response
jest wymagany i musi być ustawiony na obiektResponse
, który zostanie zwrócony do klienta, który wysłał żądanie. Jeśli podasz coś innego niż prawidłowa wartośćResponse
, żądanie klienta zostanie zakończone z błędem sieci. W odróżnieniu od wywołania funkcjirespondWith()
w modułach obsługi zdarzeńfetch
musisz podać tutaj wartośćResponse
, a niePromise
, która jest interpretowana jakoResponse
. Odpowiedź możesz tworzyć za pomocą łańcucha obietnic i przekazywać go jako parametr do metodyforeignfetch
respondWith()
, ale łańcuch musi zawierać obiekt z właściwościąresponse
ustawioną na obiektResponse
. Przykładowy kod znajdziesz powyżej.- Parametr
origin
jest opcjonalny i służy do określania, czy zwracana odpowiedź jest przezroczysta. Jeśli tego nie zrobisz, odpowiedź będzie nieprzejrzysta, a klient będzie mieć ograniczony dostęp do treści i nagłówków odpowiedzi. Jeśli żądanie zostało wysłane z parametremmode: 'cors'
, zwrócenie odpowiedzi bez przejrzystości zostanie potraktowane jako błąd. Jeśli jednak określisz wartość ciągu równą pochodzeniu klienta zdalnego (którą można uzyskać za pomocą interfejsuevent.origin
), wyraźnie wyrażasz zgodę na przekazywanie klientowi odpowiedzi z włączoną obsługą CORS. - Identyfikator
headers
jest też opcjonalny i przydaje się tylko wtedy, gdy podajesz też identyfikatororigin
i zwracasz odpowiedź CORS. Domyślnie w odpowiedzi będą uwzględniane tylko nagłówki z listy nagłówków odpowiedzi bezpiecznych w świetle CORS. Jeśli chcesz dodatkowo filtrować zwracane dane, możesz podać listę nazw nagłówków, które mają być uwzględnione w odpowiedzi. Dzięki temu możesz włączyć CORS, a jednocześnie uniemożliwić udostępnianie potencjalnie poufnych nagłówków odpowiedzi bezpośrednio klientowi zdalnym.
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 elementu usługi 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. Wymóg wyrażenia zgody na odpowiedzi CORS to jeden z kroków do ograniczenia niezamierzonej ekspozycji na koronawirusa. Jako deweloper możesz jednak w swoim module obsługi foreignfetch
wyraźnie wysyłać żądania fetch()
, które nie używają domniemanych danych logowania za pomocą:
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
Występują dodatkowe kwestie, które mają wpływ na to, jak zagraniczny skrypt usługi pobierania obsługuje żądania wysyłane od klientów tej usługi.
Klienci, którzy mają własny skrypt service worker
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 w ramach workera ma pierwszeństwo w reagowaniu na wszystkie żądania wysyłane przez aplikację internetową, nawet jeśli istnieje usługa zewnętrzna 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ń dotyczących zasobu w innej domenie, żą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()
do żądania zasobu w zakresie 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ętrznych, które wdrażają zewnętrznego pracownika usługi pobierania, nawet jeśli nie korzystają jeszcze z własnego pracownika usługi. Klienci nie muszą nic robić, aby zacząć korzystać z obsługiwanego przez zewnętrzny proces obsługiwany przez usługę, o ile używają przeglądarki, która ją 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.
fetch
handler (jeśli występuje) skryptu service worker firmy zewnętrznejforeignfetch
handler (jeśli jest obecny i tylko w przypadku żądań między domenami) usługi wątek usługi zewnętrznej- Pamięć podręczna HTTP przeglądarki (jeśli istnieje nowa odpowiedź)
- 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
- Wyjaśnienie pobierania obcych
- Przykładowy kod i prezentacja na żywo
- Narzędzie do śledzenia problemów z usługami działającymi w tle
Bądź na bieżąco
Implementacja w Chrome testowania origin z obsługą pobierania z innych domen 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 poniżej w miarę ich wprowadzania. Informacje o istotnych zmianach będziemy też udostępniać na koncie @chromiumdev na Twitterze.