WebSocketStream: การผสานรวมสตรีมกับ WebSocket API

ป้องกันไม่ให้แอปจมอยู่กับข้อความของ WebSocket หรือทำให้ข้อความในเซิร์ฟเวอร์ WebSocket ท่วมท้นด้วยการใช้การกด Backpress

ที่มา

WebSocket API

WebSocket API มีอินเทอร์เฟซ JavaScript สำหรับโปรโตคอล WebSocket ซึ่งทำให้สามารถเปิดเซสชันการสื่อสารแบบอินเทอร์แอกทีฟแบบ 2 ทางระหว่างเบราว์เซอร์ของผู้ใช้กับเซิร์ฟเวอร์ได้ เมื่อใช้ API นี้ คุณจะส่งข้อความไปยังเซิร์ฟเวอร์และรับการตอบกลับที่ขับเคลื่อนด้วยเหตุการณ์ได้โดยไม่ต้องสำรวจเซิร์ฟเวอร์เพื่อดูการตอบกลับ

Streams API

Streams API ช่วยให้ JavaScript เข้าถึงสตรีมข้อมูลของกลุ่มข้อมูลที่ได้รับผ่านเครือข่ายแบบเป็นโปรแกรมและประมวลผลได้ตามต้องการ แนวคิดสำคัญในบริบทของสตรีมคือกดดันย้อนกลับ นี่เป็นกระบวนการที่สตรีมเดียวหรือเชนไปป์จะควบคุมความเร็วในการอ่านหรือเขียน เมื่อตัวสตรีมเองหรือสตรีมในเชนไปป์ยังไม่ว่างและยังไม่พร้อมรับท่อนอื่นๆ สตรีมจะส่งสัญญาณย้อนกลับผ่านเชนเพื่อให้การนำส่งช้าตามความเหมาะสม

ปัญหาเกี่ยวกับ WebSocket API ปัจจุบัน

ไม่สามารถกดดันเครื่องหมายกดลงในข้อความที่ได้รับ

เมื่อใช้ WebSocket API ปัจจุบัน การแสดงความรู้สึกต่อข้อความจะเกิดขึ้นใน WebSocket.onmessage ซึ่งเป็น EventHandler ที่เรียกใช้เมื่อได้รับข้อความจากเซิร์ฟเวอร์

สมมติว่าคุณมีแอปพลิเคชันที่ต้องดำเนินการปรับเปลี่ยนข้อมูลจำนวนมากเมื่อได้รับข้อความใหม่ คุณอาจตั้งค่าขั้นตอนคล้ายๆ กับโค้ดด้านล่างนี้ และเนื่องจากคุณawaitผลของการเรียก process() คุณก็จะทำได้ดีใช่ไหม

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

ผิด! ปัญหาของ WebSocket API ปัจจุบันคือไม่มีวิธีใช้ Backpressure เมื่อข้อความมาถึงเร็วกว่าที่วิธีที่ process() จัดการได้ กระบวนการแสดงผลจะเต็มหน่วยความจำด้วยการบัฟเฟอร์ข้อความเหล่านั้น หรืออาจไม่ตอบสนองเนื่องจากการใช้งาน CPU 100% หรือทั้งสองอย่าง

การกดแรงดันต่ำกับข้อความที่ส่งไม่เป็นไปตามสรีรศาสตร์

ระบบใช้แรงกดดันต่ำกับข้อความที่ส่งได้ แต่ก็ต้องทำการสำรวจพร็อพเพอร์ตี้ WebSocket.bufferedAmount ซึ่งไร้ประสิทธิภาพและไม่เป็นไปตามสรีรศาสตร์ พร็อพเพอร์ตี้แบบอ่านอย่างเดียวนี้จะแสดงจำนวนไบต์ของข้อมูลที่อยู่ในคิวโดยใช้การเรียกใช้ไปยัง WebSocket.send() แต่ยังไม่ได้ส่งไปยังเครือข่าย ค่านี้จะรีเซ็ตเป็น 0 เมื่อส่งข้อมูลในคิวทั้งหมดแล้ว แต่หากคุณเรียกใช้ WebSocket.send() ต่อไป ค่าก็จะเพิ่มขึ้นเรื่อยๆ

WebSocketStream API คืออะไร

WebSocketStream API จัดการกับปัญหาแรงดันย้อนกลับที่ไม่มีจริงหรือไม่ทำงานโดยการผสานรวมสตรีมกับ WebSocket API ซึ่งหมายความว่าคุณสามารถใช้แรงกดดันต่ำได้ "ฟรี" โดยไม่มีค่าใช้จ่ายเพิ่มเติม

กรณีการใช้งานที่แนะนำสำหรับ WebSocketStream API

ตัวอย่างของเว็บไซต์ที่ใช้ API นี้ได้มีดังนี้

  • แอปพลิเคชัน WebSocket แบนด์วิดท์สูงที่ต้องคงการโต้ตอบไว้ โดยเฉพาะวิดีโอและการแชร์หน้าจอ
  • ในทำนองเดียวกัน การจับภาพวิดีโอและแอปพลิเคชันอื่นๆ ที่สร้างข้อมูลจำนวนมากในเบราว์เซอร์ซึ่งจำเป็นต้องอัปโหลดไปยังเซิร์ฟเวอร์ เมื่อใช้ Backpressure ไคลเอ็นต์จะหยุดสร้างข้อมูลแทนที่จะสะสมข้อมูลในหน่วยความจำ

สถานะปัจจุบัน

| ขั้นตอน | สถานะ | | ------------------------------------------ | ---------------------------- | | 1. สร้างคำอธิบาย | [สมบูรณ์][explainer] | | 2. สร้างฉบับร่างเริ่มต้นของข้อกำหนด | [กำลังดำเนินการ][spec] | | 3. รวบรวมความคิดเห็นและทำซ้ำเกี่ยวกับการออกแบบ | [กำลังดำเนินการ](#feedback) | | 4. ช่วงทดลองใช้จากต้นทาง | [เสร็จสมบูรณ์][ot] | | 5. เปิดตัว | ยังไม่เริ่ม |

วิธีใช้ WebSocketStream API

ตัวอย่างข้อมูลเบื้องต้น

WebSocketStream API เป็นไปตามสัญญา ซึ่งทำให้การจัดการกับ JavaScript นั้นดูเป็นธรรมชาติในโลก JavaScript ยุคใหม่ เริ่มต้นด้วยการสร้าง WebSocketStream ใหม่และส่ง URL ของเซิร์ฟเวอร์ WebSocket ให้กับไฟล์ดังกล่าว ต่อไป ให้คุณรอให้การเชื่อมต่อเป็น opened ซึ่งส่งผลให้เกิด ReadableStream และ/หรือ WritableStream

เมื่อเรียกใช้เมธอด ReadableStream.getReader() คุณจะได้รับ ReadableStreamDefaultReader ในท้ายที่สุด ซึ่งคุณจะได้รับข้อมูล read() จนกว่าสตรีมจะเสร็จสิ้น กล่าวคือ จนกว่าสตรีมจะส่งคืนออบเจ็กต์ของแบบฟอร์ม {value: undefined, done: true}

ดังนั้น เมื่อเรียกใช้เมธอด WritableStream.getWriter() คุณจะได้รับ WritableStreamDefaultWriter ซึ่งใช้เก็บข้อมูล write() ได้

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

แรงดันสูง

แล้วฟีเจอร์ Backpressure คืออะไร ตามที่ฉันเขียนด้านบน คุณจะได้รับสิทธิ์ "ฟรี" โดยไม่มีขั้นตอนเพิ่มเติมใดๆ หาก process() ใช้เวลาเกิน ระบบจะใช้ข้อความถัดไปเมื่อไปป์ไลน์พร้อมใช้งานเท่านั้น ในทำนองเดียวกัน ขั้นตอน WritableStreamDefaultWriter.write() จะดําเนินการต่อเมื่อปลอดภัยเท่านั้น

ตัวอย่างขั้นสูง

อาร์กิวเมนต์ที่ 2 ของ WebSocketStream คือขอบเขตตัวเลือกสำหรับการขยายในอนาคต ปัจจุบันตัวเลือกเดียวที่มีคือ protocols ซึ่งทำงานเหมือนกับอาร์กิวเมนต์ที่ 2 ของตัวสร้าง WebSocket

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

protocol ที่เลือกและ extensions ที่เป็นไปได้เป็นส่วนหนึ่งของพจนานุกรมที่มีให้บริการผ่าน WebSocketStream.opened สัญญา ข้อมูลทั้งหมดเกี่ยวกับการเชื่อมต่อแบบสดได้มาจากสัญญานี้ เนื่องจากข้อมูลดังกล่าวไม่เกี่ยวข้องหากการเชื่อมต่อล้มเหลว

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

ข้อมูลเกี่ยวกับการเชื่อมต่อ WebSocketStream แบบปิด

ข้อมูลที่มีอยู่จากเหตุการณ์ WebSocket.onclose และ WebSocket.onerror ใน WebSocket API พร้อมให้ใช้งานแล้วผ่าน WebSocketStream.closed สัญญา สัญญาจะปฏิเสธในกรณีที่การปิดไม่เรียบร้อย มิฉะนั้น ระบบจะแก้ไขโค้ดและเหตุผลที่เซิร์ฟเวอร์ส่ง

รหัสสถานะทั้งหมดที่เป็นไปได้และความหมายของรหัสสถานะนั้นอธิบายไว้ในรายการรหัสสถานะ CloseEvent

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

การปิดการเชื่อมต่อ WebSocketStream

ซึ่ง WebSocketStream สามารถปิดได้ด้วย AbortController ดังนั้น ให้ส่ง AbortSignal ไปยังเครื่องมือสร้าง WebSocketStream

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

หรือคุณจะใช้เมธอด WebSocketStream.close() ก็ได้ แต่วัตถุประสงค์หลักของวิธีการคืออนุญาตให้ระบุโค้ดและเหตุผลที่ส่งไปยังเซิร์ฟเวอร์

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

การเพิ่มประสิทธิภาพแบบต่อเนื่องและความสามารถในการทำงานร่วมกัน

ปัจจุบัน Chrome เป็นเบราว์เซอร์เดียวที่ใช้ WebSocketStream API สำหรับความสามารถในการทำงานร่วมกับ WebSocket API แบบคลาสสิก จะไม่สามารถใช้การกดดันภายหลังกับข้อความที่ได้รับ ระบบใช้แรงกดดันต่ำกับข้อความที่ส่งได้ แต่ก็ต้องทำการสำรวจพร็อพเพอร์ตี้ WebSocket.bufferedAmount ซึ่งไร้ประสิทธิภาพและไม่เป็นไปตามสรีรศาสตร์

การตรวจหาฟีเจอร์

หากต้องการตรวจสอบว่าระบบรองรับ WebSocketStream API หรือไม่ ให้ใช้

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

เดโม

ในเบราว์เซอร์ที่รองรับ คุณจะดู WebSocketStream API ที่ใช้งานได้ใน iframe ที่ฝังไว้หรือใน Glitch โดยตรงก็ได้

ความคิดเห็น

ทีม Chrome ต้องการทราบข้อมูลเกี่ยวกับประสบการณ์การใช้งาน WebSocketStream API ของคุณ

บอกให้เราทราบเกี่ยวกับการออกแบบ API

มีบางอย่างเกี่ยวกับ API ที่ทำงานผิดปกติไหม หรือมีวิธีหรือคุณสมบัติที่จำเป็นที่คุณจำเป็นต้องใช้ในการนำความคิดของคุณไปปฏิบัติหรือไม่ มีคำถามหรือความคิดเห็นเกี่ยวกับรูปแบบการรักษาความปลอดภัยหรือไม่ แจ้งปัญหาข้อมูลจำเพาะในที่เก็บ GitHub ที่เกี่ยวข้อง หรือเพิ่มความเห็นของคุณเกี่ยวกับปัญหาที่มีอยู่

รายงานปัญหาเกี่ยวกับการติดตั้งใช้งาน

คุณพบข้อบกพร่องในการใช้งาน Chrome หรือไม่ หรือการใช้งานแตกต่างจากข้อกำหนดหรือไม่ รายงานข้อบกพร่องที่ new.crbug.com อย่าลืมใส่รายละเอียดให้มากที่สุดเท่าที่จะทำได้ ดูวิธีการง่าย ๆ ในการจำลองซ้ำ และป้อน Blink>Network>WebSockets ลงในช่องคอมโพเนนต์ Glitch เหมาะสำหรับการแชร์เคสการทำซ้ำที่รวดเร็วและง่ายดาย

แสดงการสนับสนุนสำหรับ API

คุณวางแผนที่จะใช้ WebSocketStream API ใช่ไหม การสนับสนุนสาธารณะของคุณช่วยให้ทีม Chrome จัดลำดับความสำคัญของคุณลักษณะ และแสดงให้ผู้ให้บริการเบราว์เซอร์รายอื่นๆ เห็นว่าการสนับสนุนนั้นสำคัญเพียงใด

ส่งทวีตไปที่ @ChromiumDev โดยใช้แฮชแท็ก #WebSocketStream และบอกให้เราทราบว่าคุณใช้แฮชแท็กที่ใดและอย่างไร

ลิงก์ที่มีประโยชน์

กิตติกรรมประกาศ

Adam Rice และ Yutaka Hirano ติดตั้งใช้งาน WebSocketStream API รูปภาพหลักของ Daan Mooij ใน Unsplash