WebSocketStream: mengintegrasikan streaming dengan WebSocket API

Cegah aplikasi Anda tenggelam dalam pesan WebSocket atau membanjiri server WebSocket dengan pesan dengan menerapkan tekanan balik.

Latar belakang

WebSocket API

WebSocket API menyediakan antarmuka JavaScript ke protokol WebSocket, yang memungkinkan pembukaan sesi komunikasi interaktif dua arah antara browser pengguna dan server. Dengan API ini, Anda dapat mengirim pesan ke server dan menerima respons berbasis peristiwa tanpa polling server untuk mendapatkan balasan.

Streams API

Dengan Streams API, JavaScript dapat mengakses aliran potongan data yang diterima melalui jaringan dan memprosesnya sesuai keinginan secara terprogram. Konsep penting dalam konteks streaming adalah tekanan balik. Ini adalah proses saat satu aliran atau rantai pipa mengatur kecepatan membaca atau menulis. Saat stream itu sendiri atau stream berikutnya dalam rantai pipe masih sibuk dan belum siap untuk menerima lebih banyak potongan, sistem ini akan mengirimkan sinyal ke belakang melalui rantai untuk memperlambat pengiriman sebagaimana mestinya.

Masalah dengan WebSocket API saat ini

Menerapkan tekanan balik ke pesan yang diterima tidak mungkin

Dengan WebSocket API saat ini, reaksi terhadap pesan terjadi di WebSocket.onmessage, EventHandler yang dipanggil saat pesan diterima dari server.

Anggaplah Anda memiliki aplikasi yang perlu melakukan operasi pemrosesan data yang berat setiap kali pesan baru diterima. Anda mungkin akan menyiapkan alur yang mirip dengan kode di bawah ini, dan karena Anda melakukan await sebagai hasil panggilan process(), seharusnya Anda akan baik-baik saja, bukan?

// A heavy data crunching operation.
const process = async (data) => {
  return new Promise((resolve) => {
    window.setTimeout(() => {
      console.log('WebSocket message processed:', data);
      return resolve('done');
    }, 1000);
  });
};

webSocket.onmessage = async (event) => {
  const data = event.data;
  // Await the result of the processing step in the message handler.
  await process(data);
};

Salah! Masalah dengan WebSocket API saat ini adalah tidak ada cara untuk menerapkan tekanan balik. Jika pesan tiba lebih cepat daripada metode process() yang dapat menanganinya, proses render akan mengisi memori dengan melakukan buffering pesan tersebut, menjadi tidak responsif karena penggunaan CPU 100%, atau keduanya.

Menerapkan tekanan balik ke pesan yang dikirim tidak ergonomis

Menerapkan tekanan balik ke pesan yang dikirim dapat dilakukan, tetapi melibatkan polling properti WebSocket.bufferedAmount, yang tidak efisien dan tidak ergonomis. Properti hanya baca ini menampilkan jumlah byte data yang telah dimasukkan dalam antrean menggunakan panggilan ke WebSocket.send(), tetapi belum ditransmisikan ke jaringan. Nilai ini direset ke nol setelah semua data dalam antrean dikirim, tetapi jika Anda terus memanggil WebSocket.send(), nilai tersebut akan terus meningkat.

Apa yang dimaksud dengan WebSocketStream API?

WebSocketStream API menangani masalah tekanan balik yang tidak ada atau tidak ergonomis dengan mengintegrasikan stream dengan WebSocket API. Ini berarti backpressure dapat diterapkan "secara gratis", tanpa biaya tambahan.

Kasus penggunaan yang disarankan untuk WebSocketStream API

Contoh situs yang dapat menggunakan API ini meliputi:

  • Aplikasi WebSocket bandwidth tinggi yang perlu mempertahankan interaktivitas, khususnya video dan berbagi layar.
  • Demikian pula, perekaman video dan aplikasi lain yang menghasilkan banyak data di browser yang perlu diupload ke server. Dengan tekanan balik, klien dapat berhenti memproduksi data alih-alih mengumpulkan data dalam memori.

Status saat ini

| Langkah | Status | | ------------------------------------------ | ---------------------------- | | 1. Buat penjelasan | [Selesai][penjelas] | | 2. Buat draf awal spesifikasi | [Dalam proses][spesifikasi] | | 3. Mengumpulkan masukan & melakukan iterasi desain | [Dalam proses](#feedback) | | 4. Uji coba origin | [Selesai][ot] | | 5. Peluncuran | Belum dimulai |

Cara menggunakan WebSocketStream API

Contoh pendahuluan

WebSocketStream API berbasis promise, yang membuat penanganannya terasa alami di dunia JavaScript modern. Anda memulai dengan membuat WebSocketStream baru dan meneruskan URL server WebSocket ke sini. Selanjutnya, Anda menunggu koneksi menjadi opened, yang menghasilkan ReadableStream dan/atau WritableStream.

Dengan memanggil metode ReadableStream.getReader(), akhirnya Anda mendapatkan ReadableStreamDefaultReader, yang kemudian dapat Anda read() gunakan data hingga aliran data selesai, yaitu hingga objek berformat {value: undefined, done: true} ditampilkan.

Oleh karena itu, dengan memanggil metode WritableStream.getWriter(), Anda akhirnya mendapatkan WritableStreamDefaultWriter, yang kemudian dapat Anda gunakan untuk write() data.

  const wss = new WebSocketStream(WSS_URL);
  const {readable, writable} = await wss.opened;
  const reader = readable.getReader();
  const writer = writable.getWriter();

  while (true) {
    const {value, done} = await reader.read();
    if (done) {
      break;
    }
    const result = await process(value);
    await writer.write(result);
  }

Tekanan Belakang

Bagaimana dengan fitur tekanan balik yang dijanjikan? Seperti yang saya tulis di atas, Anda mendapatkannya "secara gratis", tidak perlu langkah tambahan. Jika process() memerlukan waktu tambahan, pesan berikutnya hanya akan digunakan setelah pipeline siap. Demikian pula, langkah WritableStreamDefaultWriter.write() hanya akan dilanjutkan jika aman untuk melakukannya.

Contoh lanjutan

Argumen kedua untuk WebSocketStream adalah tas opsi untuk memungkinkan ekstensi di masa mendatang. Saat ini, satu-satunya opsi adalah protocols, yang berperilaku sama dengan argumen kedua untuk konstruktor WebSocket:

const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;

protocol yang dipilih serta kemungkinan extensions adalah bagian dari kamus yang tersedia melalui promise WebSocketStream.opened. Semua informasi tentang koneksi langsung disediakan oleh promise ini, karena tidak relevan jika koneksi gagal.

const {readable, writable, protocol, extensions} = await chatWSS.opened;

Informasi tentang koneksi WebSocketStream tertutup

Informasi yang sebelumnya tersedia dari peristiwa WebSocket.onclose dan WebSocket.onerror di WebSocket API kini tersedia melalui promise WebSocketStream.closed. Promise akan ditolak jika penutupan tidak rapi. Jika tidak, promise akan di-resolve ke kode dan alasan yang dikirim oleh server.

Semua kemungkinan kode status dan artinya dijelaskan dalam daftar kode status CloseEvent.

const {code, reason} = await chatWSS.closed;

Menutup koneksi WebSocketStream

WebSocketStream dapat ditutup dengan AbortController. Oleh karena itu, teruskan AbortSignal ke konstruktor WebSocketStream.

const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);

Sebagai alternatif, Anda juga dapat menggunakan metode WebSocketStream.close(), tetapi tujuan utamanya adalah mengizinkan penentuan kode dan alasan yang dikirim ke server.

wss.close({code: 4000, reason: 'Game over'});

{i>Progressive enhancement<i} dan interoperabilitas

Saat ini Chrome adalah satu-satunya browser yang menerapkan WebSocketStream API. Untuk interoperabilitas dengan WebSocket API klasik, penerapan tekanan balik ke pesan yang diterima tidak dapat dilakukan. Menerapkan tekanan balik ke pesan yang dikirim dapat dilakukan, tetapi melibatkan polling properti WebSocket.bufferedAmount, yang tidak efisien dan tidak ergonomis.

Deteksi fitur

Untuk memeriksa apakah WebSocketStream API didukung, gunakan:

if ('WebSocketStream' in window) {
  // `WebSocketStream` is supported!
}

Demo

Di browser yang mendukung, Anda dapat melihat cara kerja WebSocketStream API dalam iframe yang disematkan, atau langsung di Glitch.

Masukan

Tim Chrome ingin mengetahui tentang pengalaman Anda dengan WebSocketStream API.

Beri tahu kami tentang desain API

Apakah ada sesuatu pada API yang tidak bekerja seperti yang Anda harapkan? Atau apakah ada metode atau properti yang hilang yang diperlukan untuk menerapkan ide Anda? Punya pertanyaan atau komentar tentang model keamanan? Laporkan masalah spesifikasi di repo GitHub yang sesuai, atau tambahkan pendapat Anda ke masalah yang sudah ada.

Melaporkan masalah terkait penerapan

Apakah Anda menemukan bug pada implementasi Chrome? Atau apakah implementasinya berbeda dengan spesifikasi? Laporkan bug di new.crbug.com. Pastikan untuk menyertakan detail sebanyak mungkin, petunjuk sederhana untuk mereproduksi, dan masukkan Blink>Network>WebSockets dalam kotak Komponen. Glitch sangat cocok untuk membagikan kasus reproduksi yang cepat dan mudah.

Menunjukkan dukungan untuk API

Apakah Anda berencana menggunakan WebSocketStream API? Dukungan publik Anda membantu tim Chrome memprioritaskan fitur dan menunjukkan kepada vendor browser lain betapa pentingnya mendukung fitur tersebut.

Kirim tweet ke @ChromiumDev menggunakan hashtag #WebSocketStream dan beri tahu kami di mana dan bagaimana Anda menggunakannya.

Link bermanfaat

Ucapan terima kasih

WebSocketStream API diterapkan oleh Adam Rice dan Yutaka Hirano. Banner besar oleh Daan Mooij di Unsplash.