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

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

ข้อมูลเบื้องต้น

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

Streams API

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

ปัญหาเกี่ยวกับ 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% หรือทั้ง 2 อย่าง

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

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

WebSocketStream API คืออะไร

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

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

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

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

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

ขั้นตอน สถานะ
1. สร้างคําอธิบาย เสร็จสมบูรณ์
2. สร้างฉบับร่างแรกของข้อกําหนด กำลังดำเนินการ
3. รวบรวมความคิดเห็นและทำซ้ำเกี่ยวกับการออกแบบ กำลังดำเนินการ
4. ช่วงทดลองใช้จากต้นทาง เสร็จสมบูรณ์
5. เปิดตัว ยังไม่เริ่ม

วิธีใช้ WebSocketStream API

WebSocketStream API ทำงานตามสัญญา ซึ่งทำให้การใช้งาน API นี้รู้สึกเป็นธรรมชาติใน 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() จะดำเนินการต่อก็ต่อเมื่อทำได้อย่างปลอดภัยเท่านั้น

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

อาร์กิวเมนต์ที่สองของ 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.opened

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

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

รหัสสถานะที่เป็นไปได้ทั้งหมดและความหมายมีอธิบายอยู่ในรายการรหัสสถานะ 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 และแจ้งให้เราทราบว่าคุณใช้ฟีเจอร์นี้ที่ไหนและอย่างไร

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

ขอขอบคุณ

WebSocketStream API ดำเนินการโดย Adam Rice และ Yutaka Hirano