Pekerja Layanan Lintas asal - Bereksperimen dengan Pengambilan Asing

Latar belakang

Service worker memberi developer web kemampuan untuk merespons permintaan jaringan yang dibuat oleh aplikasi web mereka, sehingga mereka dapat terus bekerja meskipun sedang offline, melawan lie-fi, dan menerapkan interaksi cache yang kompleks seperti stale-while-revalidate. Namun, service worker secara historis terikat dengan origin tertentu—sebagai pemilik aplikasi web, Anda bertanggung jawab untuk menulis dan men-deploy service worker guna mencegat semua permintaan jaringan yang dibuat aplikasi web Anda. Dalam model tersebut, setiap pekerja layanan bertanggung jawab untuk menangani bahkan permintaan lintas-asal, misalnya ke API pihak ketiga atau untuk font web.

Bagaimana jika penyedia API pihak ketiga, atau font web, atau layanan umum lainnya memiliki kemampuan untuk men-deploy pekerja layanan mereka sendiri yang memiliki peluang untuk menangani permintaan yang dibuat oleh origin lain ke origin mereka? Penyedia dapat menerapkan logika jaringan kustom mereka sendiri, dan memanfaatkan satu instance cache yang kredibel untuk menyimpan respons mereka. Sekarang, berkat pengambilan asing, jenis deployment pekerja layanan pihak ketiga tersebut menjadi kenyataan.

Men-deploy pekerja layanan yang menerapkan pengambilan asing akan bermanfaat bagi penyedia layanan apa pun yang diakses melalui permintaan HTTPS dari browser—cukup pikirkan skenario saat Anda dapat menyediakan versi layanan yang tidak bergantung pada jaringan, tempat browser dapat memanfaatkan cache resource umum. Layanan yang dapat memanfaatkannya mencakup, tetapi tidak terbatas pada:

  • Penyedia API dengan antarmuka RESTful
  • Penyedia font web
  • Penyedia analisis
  • Penyedia hosting gambar
  • Jaringan penayangan konten generik

Bayangkan, misalnya, Anda adalah penyedia analisis. Dengan men-deploy pekerja layanan pengambilan asing, Anda dapat memastikan bahwa semua permintaan ke layanan Anda yang gagal saat pengguna offline akan diantrekan dan di-replay setelah konektivitas kembali. Meskipun klien layanan dapat menerapkan perilaku serupa melalui pekerja layanan pihak pertama, mengharuskan setiap klien menulis logika khusus untuk layanan Anda tidak sebesar mengandalkan pekerja layanan pengambilan asing bersama yang Anda deploy.

Prasyarat

Token Uji Coba Origin

Pengambilan asing masih dianggap eksperimental. Agar desain ini tidak terburu-buru sebelum ditentukan sepenuhnya dan disepakati oleh vendor browser, desain tersebut telah diimplementasikan di Chrome 54 sebagai Uji Coba Origin. Selama pengambilan asing masih bersifat eksperimental, untuk menggunakan fitur baru ini dengan layanan yang Anda host, Anda harus meminta token yang dicakup untuk asal layanan tertentu. Token harus disertakan sebagai header respons HTTP di semua permintaan lintas-asal untuk resource yang ingin Anda tangani melalui pengambilan asing, serta dalam respons untuk resource JavaScript pekerja layanan Anda:

Origin-Trial: token_obtained_from_signup

Uji coba ini akan berakhir pada Maret 2017. Pada saat itu, kami berharap telah mengetahui perubahan yang diperlukan untuk menstabilkan fitur, dan (semoga) mengaktifkannya secara default. Jika pengambilan asing tidak diaktifkan secara default pada saat itu, fungsi yang terikat dengan token Uji Coba Origin yang ada akan berhenti berfungsi.

Untuk memfasilitasi eksperimen dengan pengambilan asing sebelum mendaftar untuk token Uji Coba Origin resmi, Anda dapat mengabaikan persyaratan di Chrome untuk komputer lokal dengan membuka chrome://flags/#enable-experimental-web-platform-features dan mengaktifkan tanda "Fitur Platform Web Eksperimental". Perhatikan bahwa tindakan ini perlu dilakukan di setiap instance Chrome yang ingin Anda gunakan dalam eksperimen lokal, sedangkan dengan token Uji Coba Origin, fitur tersebut akan tersedia untuk semua pengguna Chrome Anda.

HTTPS

Seperti semua deployment pekerja layanan, server web yang Anda gunakan untuk menayangkan resource dan skrip pekerja layanan harus diakses melalui HTTPS. Selain itu, intersepsi pengambilan asing hanya berlaku untuk permintaan yang berasal dari halaman yang dihosting di origin aman, sehingga klien layanan Anda harus menggunakan HTTPS untuk memanfaatkan penerapan pengambilan asing Anda.

Menggunakan Pengambilan Asing

Setelah prasyarat selesai, mari kita pelajari detail teknis yang diperlukan untuk menyiapkan dan menjalankan pekerja layanan pengambilan asing.

Mendaftarkan pekerja layanan

Tantangan pertama yang mungkin Anda hadapi adalah cara mendaftarkan pekerja layanan. Jika sudah pernah menggunakan pekerja layanan sebelumnya, Anda mungkin sudah memahami hal-hal berikut:

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

Kode JavaScript ini untuk pendaftaran pekerja layanan pihak pertama masuk akal dalam konteks aplikasi web, yang dipicu oleh pengguna yang membuka URL yang Anda kontrol. Namun, ini bukan pendekatan yang tepat untuk mendaftarkan pekerja layanan pihak ketiga, jika satu-satunya interaksi browser dengan server Anda adalah meminta sub-resource tertentu, bukan navigasi lengkap. Jika browser meminta, misalnya, gambar dari server CDN yang Anda kelola, Anda tidak dapat menambahkan cuplikan JavaScript tersebut ke respons dan berharap cuplikan tersebut akan dijalankan. Metode pendaftaran pekerja layanan yang berbeda, di luar konteks eksekusi JavaScript normal, diperlukan.

Solusinya berupa header HTTP yang dapat disertakan server Anda dalam respons apa pun:

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

Mari kita uraikan contoh header tersebut menjadi komponennya, yang masing-masing dipisahkan oleh karakter ;.

  • </service-worker.js> diperlukan, dan digunakan untuk menentukan jalur ke file pekerja layanan (ganti /service-worker.js dengan jalur yang sesuai ke skrip Anda). Ini berkaitan langsung dengan string scriptURL yang akan diteruskan sebagai parameter pertama ke navigator.serviceWorker.register(). Nilai harus diapit dalam karakter <> (seperti yang diwajibkan oleh spesifikasi header Link), dan jika URL relatif, bukan absolut, yang diberikan, URL tersebut akan ditafsirkan sebagai relatif terhadap lokasi respons.
  • rel="serviceworker" juga diperlukan, dan harus disertakan tanpa perlu penyesuaian.
  • scope=/ adalah deklarasi cakupan opsional, setara dengan string options.scope yang dapat Anda teruskan sebagai parameter kedua ke navigator.serviceWorker.register(). Untuk banyak kasus penggunaan, Anda tidak masalah menggunakan cakupan default, jadi jangan ragu untuk menghapusnya kecuali jika Anda tahu bahwa Anda memerlukannya. Batasan yang sama seputar cakupan maksimum yang diizinkan, beserta kemampuan untuk melonggarkan batasan tersebut melalui header Service-Worker-Allowed, berlaku untuk pendaftaran header Link.

Sama seperti pendaftaran pekerja layanan "tradisional", penggunaan header Link akan menginstal pekerja layanan yang akan digunakan untuk permintaan berikutnya yang dibuat terhadap cakupan terdaftar. Isi respons yang menyertakan header khusus akan digunakan apa adanya, dan langsung tersedia untuk halaman, tanpa menunggu pekerja layanan asing menyelesaikan penginstalan.

Perlu diingat bahwa pengambilan asing saat ini diterapkan sebagai Uji Coba Origin, sehingga selain header respons Link, Anda juga harus menyertakan header Origin-Trial yang valid. Kumpulan header respons minimum yang harus ditambahkan untuk mendaftarkan pekerja layanan pengambilan asing Anda adalah

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

Men-debug pendaftaran

Selama pengembangan, sebaiknya konfirmasi bahwa pekerja layanan pengambilan asing telah diinstal dan memproses permintaan dengan benar. Ada beberapa hal yang dapat Anda periksa di Developer Tools Chrome untuk mengonfirmasi bahwa semuanya berfungsi seperti yang diharapkan.

Apakah header respons yang tepat dikirim?

Untuk mendaftarkan service worker pengambilan asing, Anda perlu menetapkan header Link pada respons ke resource yang dihosting di domain Anda, seperti yang dijelaskan sebelumnya dalam postingan ini. Selama periode Uji Coba Origin, dan dengan asumsi Anda tidak menetapkan chrome://flags/#enable-experimental-web-platform-features, Anda juga perlu menetapkan header respons Origin-Trial. Anda dapat mengonfirmasi bahwa server web Anda menetapkan header tersebut dengan melihat entri di panel Jaringan DevTools:

Header yang ditampilkan di panel Jaringan.

Apakah pekerja layanan Pengambilan Luar Negeri terdaftar dengan benar?

Anda juga dapat mengonfirmasi pendaftaran pekerja layanan yang mendasarinya, termasuk cakupannya, dengan melihat daftar lengkap pekerja layanan di panel Aplikasi DevTools. Pastikan untuk memilih opsi "Tampilkan semua", karena secara default, Anda hanya akan melihat pekerja layanan untuk origin saat ini.

Pekerja layanan pengambilan asing di panel Aplikasi.

Pengendali peristiwa penginstalan

Setelah Anda mendaftarkan pekerja layanan pihak ketiga, pekerja layanan tersebut akan mendapatkan kesempatan untuk merespons peristiwa install dan activate, seperti halnya pekerja layanan lainnya. Fungsi ini dapat memanfaatkan peristiwa tersebut untuk, misalnya, mengisi cache dengan resource yang diperlukan selama peristiwa install, atau memangkas cache yang sudah tidak berlaku dalam peristiwa activate.

Selain aktivitas penyimpanan cache peristiwa install normal, ada langkah tambahan yang diperlukan di dalam pengendali peristiwa install pekerja layanan pihak ketiga Anda. Kode Anda harus memanggil registerForeignFetch(), seperti dalam contoh berikut:

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

Ada dua opsi konfigurasi, keduanya diperlukan:

  • scopes mengambil array dari satu atau beberapa string, yang masing-masing mewakili cakupan untuk permintaan yang akan memicu peristiwa foreignfetch. Tapi tunggu, Anda mungkin berpikir, Saya sudah menentukan cakupan selama pendaftaran pekerja layanan! Memang benar, dan cakupan keseluruhan tersebut masih relevan—setiap cakupan yang Anda tentukan di sini harus sama dengan atau merupakan sub-cakupan dari cakupan keseluruhan pekerja layanan. Batasan cakupan tambahan di sini memungkinkan Anda men-deploy pekerja layanan serbaguna yang dapat menangani peristiwa fetch pihak pertama (untuk permintaan yang dibuat dari situs Anda sendiri) dan peristiwa foreignfetch pihak ketiga (untuk permintaan yang dibuat dari domain lain), serta memperjelas bahwa hanya sebagian dari cakupan yang lebih besar yang akan memicu foreignfetch. Dalam praktiknya, jika Anda men-deploy pekerja layanan yang didedikasikan untuk hanya menangani peristiwa foreignfetch pihak ketiga, Anda hanya perlu menggunakan satu cakupan eksplisit yang sama dengan cakupan keseluruhan pekerja layanan Anda. Itulah yang akan dilakukan contoh di atas, menggunakan nilai self.registration.scope.
  • origins juga menggunakan array yang berisi satu atau beberapa string, dan memungkinkan Anda membatasi pengendali foreignfetch agar hanya merespons permintaan dari domain tertentu. Misalnya, jika Anda mengizinkan 'https://example.com' secara eksplisit, permintaan yang dibuat dari halaman yang dihosting di https://example.com/path/to/page.html untuk resource yang ditayangkan dari cakupan pengambilan asing akan memicu pengendali pengambilan asing, tetapi permintaan yang dibuat dari https://random-domain.com/path/to/page.html tidak akan memicu pengendali Anda. Kecuali jika Anda memiliki alasan khusus untuk hanya memicu logika pengambilan asing untuk sebagian asal jarak jauh, Anda cukup menentukan '*' sebagai satu-satunya nilai dalam array, dan semua origin akan diizinkan.

Pengendali peristiwa Forefetch

Setelah Anda menginstal service worker pihak ketiga dan mengonfigurasinya melalui registerForeignFetch(), service worker tersebut akan mendapatkan kesempatan untuk mencegat permintaan sub-resource lintas origin ke server Anda yang berada dalam cakupan pengambilan asing.

Dalam pekerja layanan pihak pertama tradisional, setiap permintaan akan memicu peristiwa fetch yang dapat direspons oleh pekerja layanan Anda. Service worker pihak ketiga kami diberi kesempatan untuk menangani peristiwa yang sedikit berbeda, bernama foreignfetch. Secara konseptual, kedua peristiwa tersebut cukup mirip, dan memberi Anda kesempatan untuk memeriksa permintaan yang masuk, dan secara opsional memberikan respons melalui 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']
        };
    })
    );
});

Meskipun memiliki kesamaan konseptual, ada beberapa perbedaan dalam praktik saat memanggil respondWith() di ForeignFetchEvent. Daripada hanya memberikan Response (atau Promise yang me-resolve dengan Response) ke respondWith(), seperti yang Anda lakukan dengan FetchEvent, Anda harus meneruskan Promise yang me-resolve dengan Objek dengan properti tertentu ke respondWith() ForeignFetchEvent:

  • response diperlukan, dan harus ditetapkan ke objek Response yang akan ditampilkan ke klien yang membuat permintaan. Jika Anda memberikan apa pun selain Response yang valid, permintaan klien akan dihentikan dengan error jaringan. Tidak seperti saat memanggil respondWith() di dalam pengendali peristiwa fetch, Anda harus memberikan Response di sini, bukan Promise yang di-resolve dengan Response. Anda dapat membuat respons melalui rantai promise, dan meneruskan rantai tersebut sebagai parameter ke respondWith() foreignfetch, tetapi rantai harus diselesaikan dengan Objek yang berisi properti response yang ditetapkan ke objek Response. Anda dapat melihat demonstrasinya dalam contoh kode di atas.
  • origin bersifat opsional, dan digunakan untuk menentukan apakah respons yang ditampilkan buram atau tidak. Jika Anda tidak menyertakannya, respons akan menjadi buram, dan klien akan memiliki akses terbatas ke isi dan header respons. Jika permintaan dibuat dengan mode: 'cors', menampilkan respons buram akan dianggap sebagai error. Namun, jika menentukan nilai string yang sama dengan asal klien jarak jauh (yang dapat diperoleh melalui event.origin), Anda secara eksplisit memilih ikut serta untuk memberikan respons yang didukung CORS kepada klien.
  • headers juga bersifat opsional, dan hanya berguna jika Anda juga menentukan origin dan menampilkan respons CORS. Secara default, hanya header dalam daftar header respons yang ada dalam daftar aman CORS yang akan disertakan dalam respons Anda. Jika Anda perlu memfilter lebih lanjut apa yang ditampilkan, header akan mengambil daftar satu atau beberapa nama header, dan akan menggunakannya sebagai daftar yang diizinkan untuk ditampilkan dalam respons. Hal ini memungkinkan Anda memilih ikut serta dalam CORS sekaligus mencegah header respons yang berpotensi sensitif diekspos langsung ke klien jarak jauh.

Perlu diperhatikan bahwa saat pengendali foreignfetch dijalankan, pengendali tersebut memiliki akses ke semua kredensial dan otoritas standby asal yang menghosting pekerja layanan. Sebagai developer yang men-deploy pekerja layanan yang mengaktifkan pengambilan asing, Anda bertanggung jawab untuk memastikan bahwa Anda tidak membocorkan data respons dengan hak istimewa yang tidak akan tersedia berdasarkan kredensial tersebut. Mewajibkan keikutsertaan untuk respons CORS adalah salah satu langkah untuk membatasi eksposur yang tidak disengaja, tetapi sebagai developer, Anda dapat secara eksplisit membuat permintaan fetch() di dalam pengendali foreignfetch yang tidak menggunakan kredensial tersirat melalui:

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

Pertimbangan klien

Ada beberapa pertimbangan tambahan yang memengaruhi cara pekerja layanan pengambilan asing menangani permintaan yang dibuat dari klien layanan Anda.

Klien yang memiliki pekerja layanan pihak pertama mereka sendiri

Beberapa klien layanan Anda mungkin sudah memiliki pekerja layanan pihak pertama mereka sendiri, yang menangani permintaan yang berasal dari aplikasi web mereka. Apa artinya ini bagi pekerja layanan pengambilan pihak ketiga dan asing Anda?

Pengendali fetch di pekerja layanan pihak pertama mendapatkan kesempatan pertama untuk merespons semua permintaan yang dibuat oleh aplikasi web, meskipun ada pekerja layanan pihak ketiga dengan foreignfetch yang diaktifkan dengan cakupan yang mencakup permintaan. Namun, klien dengan pekerja layanan pihak pertama masih dapat memanfaatkan pekerja layanan pengambilan asing Anda.

Di dalam pekerja layanan pihak pertama, penggunaan fetch() untuk mengambil resource lintas-asal akan memicu pekerja layanan pengambilan asing yang sesuai. Artinya, kode seperti berikut dapat memanfaatkan pengendali foreignfetch Anda:

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

Demikian pula, jika ada pengendali pengambilan pihak pertama, tetapi tidak memanggil event.respondWith() saat menangani permintaan untuk resource lintas asal, permintaan tersebut akan otomatis "dilewati" ke pengendali foreignfetch Anda:

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

Jika pengendali fetch pihak pertama memanggil event.respondWith(), tetapi tidak menggunakan fetch() untuk meminta resource dalam cakupan pengambilan asing, pekerja layanan pengambilan asing Anda tidak akan mendapatkan kesempatan untuk menangani permintaan.

Klien yang tidak memiliki pekerja layanan sendiri

Semua klien yang membuat permintaan ke layanan pihak ketiga bisa mendapatkan keuntungan ketika layanan tersebut men-deploy pekerja layanan pengambilan asing, meskipun klien tersebut belum menggunakan pekerja layanannya sendiri. Tidak ada hal khusus yang perlu dilakukan klien untuk memilih menggunakan pekerja layanan pengambilan asing, selama mereka menggunakan browser yang mendukungnya. Artinya, dengan men-deploy pekerja layanan pengambilan asing, logika permintaan kustom dan cache bersama Anda akan langsung menguntungkan banyak klien layanan Anda, tanpa perlu melakukan langkah lebih lanjut.

Menyatukan semuanya: tempat klien mencari respons

Dengan mempertimbangkan informasi di atas, kita dapat menyusun hierarki sumber yang akan digunakan klien untuk menemukan respons untuk permintaan lintas-asal.

  1. Pengendali fetch pekerja layanan pihak pertama (jika ada)
  2. Pengendali foreignfetch pekerja layanan pihak ketiga (jika ada, dan hanya untuk permintaan lintas-asal)
  3. Cache HTTP browser (jika ada respons baru)
  4. Jaringan

Browser dimulai dari atas dan, bergantung pada penerapan pekerja layanan, akan terus menelusuri daftar hingga menemukan sumber respons.

Pelajari lebih lanjut

Selalu dapatkan info terbaru

Implementasi Uji Coba Origin pengambilan asing Chrome dapat berubah sewaktu-waktu saat kami menangani masukan dari developer. Kami akan terus memperbarui postingan ini melalui perubahan langsung, dan akan mencatat perubahan spesifik di bawah saat perubahan tersebut terjadi. Kami juga akan membagikan informasi tentang perubahan besar melalui akun Twitter @chromiumdev.