Pekerja Layanan Lintas asal - Bereksperimen dengan Pengambilan Asing

Latar belakang

Pekerja layanan memberikan kemampuan kepada developer web untuk merespons permintaan jaringan yang dibuat oleh aplikasi web mereka, sehingga mereka dapat terus bekerja bahkan saat offline, berani berbohong, dan menerapkan interaksi cache yang kompleks seperti memvalidasi ulang. Namun, pekerja layanan secara historis terikat dengan asal tertentu—sebagai pemilik aplikasi web, Anda bertanggung jawab untuk menulis dan men-deploy pekerja layanan guna menangkap semua permintaan jaringan yang dibuat oleh aplikasi web Anda. Dalam model tersebut, setiap pekerja layanan bertanggung jawab menangani bahkan permintaan lintas origin, misalnya ke API pihak ketiga atau untuk font web.

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

Menerapkan pekerja layanan yang mengimplementasikan pengambilan asing dapat dilakukan untuk setiap penyedia layanan yang diakses melalui permintaan HTTPS dari browser—cukup pikirkan skenario saat Anda dapat menyediakan versi layanan yang tidak bergantung pada jaringan, yang memungkinkan browser memanfaatkan cache resource umum. Layanan yang dapat memanfaatkan hal ini mencakup, tetapi tidak terbatas pada:

  • Penyedia API dengan antarmuka RESTful
  • Penyedia font web
  • Penyedia analisis
  • Penyedia hosting gambar
  • Jaringan penayangan konten (CDN) umum

Bayangkan, misalnya, Anda adalah penyedia analisis. Dengan men-deploy pekerja layanan pengambilan asing, Anda dapat memastikan bahwa semua permintaan ke layanan yang gagal saat pengguna sedang offline diantrekan dan diputar ulang setelah konektivitas kembali. Meskipun klien layanan dapat menerapkan perilaku serupa melalui pekerja layanan pihak pertama, mewajibkan setiap klien untuk menulis logika yang disesuaikan untuk layanan Anda tidaklah skalabel seperti mengandalkan pekerja layanan pengambilan asing bersama yang Anda deploy.

Prasyarat

Token Uji Coba Origin

Pengambilan asing masih dianggap eksperimental. Agar desain ini tidak diluncurkan secara prematur sebelum ditentukan sepenuhnya dan disetujui oleh vendor browser, desain ini telah diimplementasikan di Chrome 54 sebagai Uji Coba Origin. Selama pengambilan dari luar negeri tetap bersifat eksperimental, untuk menggunakan fitur baru ini dengan layanan yang dihosting, Anda harus meminta token yang dicakupkan ke asal khusus layanan Anda. Token harus disertakan sebagai header respons HTTP di semua permintaan lintas origin untuk resource yang ingin Anda tangani melalui pengambilan asing, serta dalam respons untuk resource JavaScript pekerja layanan:

Origin-Trial: token_obtained_from_signup

Uji coba akan berakhir pada Maret 2017. Pada tahap 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 terkait dengan token Uji Coba Origin yang ada akan berhenti berfungsi.

Untuk memfasilitasi eksperimen dengan pengambilan asing sebelum mendaftar ke 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". Perlu diperhatikan bahwa hal ini perlu dilakukan di setiap instance Chrome yang ingin digunakan 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 melayani 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 yang aman, sehingga klien layanan Anda perlu menggunakan HTTPS untuk memanfaatkan penerapan pengambilan asing oleh Anda.

Menggunakan Pengambilan Asing

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

Mendaftarkan pekerja layanan Anda

Tantangan pertama yang mungkin Anda hadapi adalah cara mendaftarkan pekerja layanan. Jika pernah bekerja dengan pekerja layanan, Anda mungkin familier dengan hal berikut:

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

Kode JavaScript untuk pendaftaran pekerja layanan pihak pertama ini masuk akal dalam konteks aplikasi web, yang dipicu oleh pengguna yang membuka URL yang Anda kontrol. Namun, ini bukanlah pendekatan yang tepat untuk mendaftarkan pekerja layanan pihak ketiga, jika satu-satunya browser interaksi yang akan dilakukan dengan server Anda adalah meminta subresource 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 oleh server Anda dalam respons apa pun:

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

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

  • </service-worker.js> diperlukan, dan digunakan untuk menentukan jalur ke file pekerja layanan Anda (ganti /service-worker.js dengan jalur yang sesuai ke skrip Anda). Ini sama 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 disediakan URL relatif, bukan URL absolut, nilai 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 dalam menggunakan cakupan default, jadi jangan ragu untuk mengabaikannya kecuali jika Anda merasa membutuhkannya. Pembatasan 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", menggunakan header Link akan menginstal pekerja layanan yang akan digunakan untuk permintaan berikutnya yang dibuat terhadap cakupan yang 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 di samping header respons Link, Anda juga harus menyertakan header Origin-Trial yang valid. Kumpulan minimum header respons yang ditambahkan untuk mendaftarkan pekerja layanan pengambilan asing Anda adalah

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

Pendaftaran proses debug

Selama pengembangan, Anda mungkin perlu mengonfirmasi 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 benar dikirim?

Untuk mendaftarkan pekerja layanan pengambilan asing, Anda perlu menyetel 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 memastikan bahwa server web Anda menyetel header tersebut dengan melihat entri di panel Jaringan DevTools:

Header ditampilkan di panel Jaringan.

Apakah pekerja layanan Foreign Fetch terdaftar dengan benar?

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

Pekerja layanan pengambilan asing di panel Aplikasi.

Pengendali peristiwa instal

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

Di luar aktivitas caching peristiwa install normal, ada langkah tambahan yang diperlukan di dalam pengendali peristiwa install pekerja layanan pihak ketiga. Kode Anda harus memanggil registerForeignFetch(), seperti pada 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 menggunakan array yang berisi satu atau beberapa string, yang masing-masing mewakili cakupan untuk permintaan yang akan memicu peristiwa foreignfetch. Namun tunggu, Anda mungkin berpikir, Saya sudah menentukan cakupan selama pendaftaran pekerja layanan! Memang benar, dan keseluruhan cakupan masih relevan—setiap cakupan yang Anda tetapkan di sini harus sama dengan atau merupakan sub-cakupan dari keseluruhan cakupan pekerja layanan. Pembatasan 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 dapat memicu foreignfetch. Dalam praktiknya, jika men-deploy pekerja layanan khusus untuk menangani peristiwa foreignfetch pihak ketiga saja, Anda hanya perlu menggunakan satu cakupan eksplisit yang sama dengan keseluruhan cakupan pekerja layanan Anda. Itulah yang akan dilakukan contoh di atas, menggunakan nilai self.registration.scope.
  • origins juga menggunakan array yang terdiri dari satu atau beberapa string, dan memungkinkan Anda membatasi pengendali foreignfetch agar hanya merespons permintaan dari domain tertentu. Misalnya, jika Anda secara eksplisit mengizinkan 'https://example.com', permintaan yang dibuat dari halaman yang dihosting di https://example.com/path/to/page.html untuk resource yang disalurkan 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. Kecuali jika Anda memiliki alasan khusus untuk hanya memicu logika pengambilan asing untuk subset origin jarak jauh, Anda dapat menentukan '*' sebagai satu-satunya nilai dalam array, dan semua origin akan diizinkan.

Pengendali peristiwaforefetch

Setelah Anda menginstal pekerja layanan pihak ketiga dan telah dikonfigurasi melalui registerForeignFetch(), pekerja layanan akan memiliki kesempatan untuk menangkap permintaan subresource lintas origin ke server Anda yang termasuk dalam cakupan pengambilan asing.

Pada pekerja layanan pihak pertama tradisional, setiap permintaan akan memicu peristiwa fetch yang dapat direspons oleh pekerja layanan Anda. Pekerja layanan pihak ketiga kita diberi kesempatan untuk menangani peristiwa yang sedikit berbeda, yang bernama foreignfetch. Secara konseptual, kedua peristiwa ini sangat 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']
        };
    })
    );
});

Terlepas dari kesamaan konseptual, ada beberapa perbedaan dalam praktik saat memanggil respondWith() di ForeignFetchEvent. Daripada hanya menyediakan Response (atau Promise yang di-resolve dengan Response) ke respondWith(), seperti yang Anda lakukan dengan FetchEvent, Anda harus meneruskan Promise yang di-resolve dengan Object dengan properti tertentu ke respondWith() ForeignFetchEvent:

  • response wajib ada dan harus disetel 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 menyediakan 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 di-resolve dengan Objek yang berisi properti response yang disetel ke objek Response. Anda dapat melihat demonstrasi hal ini dalam contoh kode di atas.
  • origin bersifat opsional, dan digunakan untuk menentukan apakah respons yang ditampilkan buram atau tidak. Jika Anda membiarkannya, 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 diperlakukan sebagai error. Namun, jika Anda menentukan nilai string yang sama dengan asal klien jarak jauh (yang dapat diperoleh melalui event.origin), berarti Anda secara eksplisit memilih ikut serta untuk memberikan respons yang diaktifkan 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 termasuk 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 header yang diizinkan untuk diekspos dalam respons. Dengan demikian, Anda dapat ikut serta dalam CORS sambil tetap mencegah header respons yang berpotensi sensitif terekspos langsung ke klien jarak jauh.

Penting untuk diperhatikan bahwa saat pengendali foreignfetch dijalankan, pengendali ini memiliki akses ke semua kredensial dan otoritas standby asal yang menghosting pekerja layanan. Sebagai developer yang men-deploy pekerja layanan dengan kemampuan pengambilan asing, Anda bertanggung jawab untuk memastikan bahwa Anda tidak membocorkan data respons istimewa apa pun yang tidak akan tersedia berdasarkan kredensial tersebut. Mewajibkan keikutsertaan untuk respons CORS adalah 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 asing pihak ketiga Anda?

Pengendali fetch dalam 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 tersebut. Namun, klien dengan pekerja layanan pihak pertama masih bisa 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 pengendali tersebut tidak memanggil event.respondWith() saat menangani permintaan untuk resource lintas-asal Anda, permintaan tersebut akan otomatis "masuk" ke pengendali 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.
});

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 berkesempatan untuk menangani permintaan tersebut.

Klien yang tidak memiliki pekerja layanan mereka sendiri

Semua klien yang membuat permintaan ke layanan pihak ketiga bisa mendapatkan manfaat saat layanan men-deploy pekerja layanan pengambilan asing, meskipun mereka belum menggunakan pekerja layanan mereka sendiri. Tidak ada hal spesifik yang perlu dilakukan klien untuk ikut serta menggunakan pekerja layanan pengambilan asing, selama mereka menggunakan browser yang mendukungnya. Ini berarti bahwa dengan men-deploy pekerja layanan pengambilan asing, logika permintaan kustom dan cache bersama Anda akan langsung bermanfaat bagi banyak klien layanan, tanpa perlu melakukan langkah lebih lanjut.

Menyatukan semuanya: di mana klien mencari tanggapan

Dengan mempertimbangkan informasi di atas, kita dapat menyusun hierarki sumber yang akan digunakan klien untuk menemukan respons atas 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 origin)
  3. Cache HTTP browser (jika ada respons baru)
  4. Jaringan

Browser memulai dari atas dan, bergantung pada implementasi pekerja layanan, akan melanjutkan daftar hingga menemukan sumber respons.

Pelajari lebih lanjut

Selalu dapatkan info terbaru

Penerapan Uji Coba Origin pengambilan asing di Chrome dapat berubah seiring kami menangani masukan dari developer. Kami akan terus memperbarui postingan ini melalui perubahan inline, dan akan memperhatikan perubahan spesifik di bawah saat terjadi. Kami juga akan membagikan informasi mengenai perubahan penting melalui akun Twitter @chromiumdev.