ป้องกันไม่ให้แอปของคุณจมอยู่กับข้อความ WebSocket หรือส่งข้อความไปยังเซิร์ฟเวอร์ WebSocket มากเกินไปโดยใช้ Backpressure
ข้อมูลเบื้องต้น
WebSocket API มีอินเทอร์เฟซ JavaScript สําหรับโปรโตคอล WebSocket ซึ่งช่วยให้เปิดเซสชันการสื่อสารแบบอินเทอร์แอกทีฟแบบ 2 ทางระหว่างเบราว์เซอร์ของผู้ใช้กับเซิร์ฟเวอร์ได้ API นี้ช่วยให้คุณส่งข้อความไปยังเซิร์ฟเวอร์และรับการตอบกลับที่ทำงานตามเหตุการณ์ได้โดยไม่ต้องตรวจสอบเซิร์ฟเวอร์เพื่อรับการตอบกลับ
Streams API
Streams API ช่วยให้คุณเข้าถึงสตรีมข้อมูลที่ได้รับผ่านเครือข่ายแบบเป็นโปรแกรม และประมวลผลข้อมูลได้ตามต้องการ แนวคิดที่สําคัญในบริบทของสตรีมคือแรงดันย้อนกลับ กระบวนการนี้คือการที่สตรีมเดียวหรือเชนไปป์ควบคุมความเร็วในการอ่านหรือเขียน เมื่อสตรีมหรือสตรีมในลำดับถัดไปในเชนไปป์ยังทำงานอยู่และยังไม่พร้อมรับข้อมูลจำนวนมากขึ้น สตรีมจะส่งสัญญาณย้อนกลับผ่านเชนเพื่อลดการส่งตามความเหมาะสม
ปัญหาเกี่ยวกับ WebSocket API ปัจจุบัน
ไม่สามารถใช้ Backpressure กับข้อความที่ได้รับ
เมื่อใช้ 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 อย่าง
การใช้แรงดันย้อนกลับกับข้อความที่ส่งไม่เป็นไปตามหลักสรีรศาสตร์
คุณใช้ Backpressure กับข้อความที่ส่งได้ แต่จะต้องทำการสำรวจพร็อพเพอร์ตี้ 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()
จะดำเนินการต่อก็ต่อเมื่อทำได้อย่างปลอดภัยเท่านั้น
ตัวอย่างขั้นสูง
อาร์กิวเมนต์ที่ 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
แล้ว
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 แบบคลาสสิก
คุณใช้ Backpressure กับข้อความที่ส่งได้ แต่จะต้องทำการสำรวจพร็อพเพอร์ตี้ WebSocket.bufferedAmount
ซึ่งไม่มีประสิทธิภาพและไม่เหมาะกับการใช้งาน
การตรวจหาองค์ประกอบ
หากต้องการตรวจสอบว่าระบบรองรับ WebSocketStream API หรือไม่ ให้ใช้คำสั่งต่อไปนี้
if ('WebSocketStream' in window) {
// `WebSocketStream` is supported!
}
สาธิต
ในเบราว์เซอร์ที่รองรับ คุณสามารถดู WebSocketStream API ทำงานได้ใน iframe ที่ฝัง หรือใน Glitch โดยตรง
ความคิดเห็น
ทีม Chrome อยากทราบความคิดเห็นของคุณเกี่ยวกับ WebSocketStream API
บอกเราเกี่ยวกับการออกแบบ API
มีสิ่งใดเกี่ยวกับ API ที่ไม่ทำงานตามที่คาดไว้ไหม หรือมีเมธอดหรือพร็อพเพอร์ตี้ที่ขาดหายไปซึ่งคุณต้องนำไปใช้กับไอเดียของคุณ หากมีคำถามหรือความคิดเห็นเกี่ยวกับรูปแบบการรักษาความปลอดภัย แจ้งปัญหาเกี่ยวกับข้อกำหนดใน GitHub repo ที่เกี่ยวข้อง หรือแสดงความคิดเห็นในปัญหาที่มีอยู่
รายงานปัญหาเกี่ยวกับการติดตั้งใช้งาน
หากพบข้อบกพร่องในการใช้งาน Chrome
หรือการติดตั้งใช้งานแตกต่างจากข้อมูลจำเพาะหรือไม่
รายงานข้อบกพร่องที่ new.crbug.com
อย่าลืมระบุรายละเอียดให้มากที่สุดเท่าที่จะเป็นไปได้ รวมถึงวิธีการง่ายๆ ในการจำลองข้อบกพร่อง และป้อน Blink>Network>WebSockets
ในช่องคอมโพเนนต์
ข้อบกพร่องเหมาะอย่างยิ่งสำหรับการแชร์เคสการเกิดข้อบกพร่องซ้ำอย่างรวดเร็วและง่ายดาย
แสดงการสนับสนุน API
คุณวางแผนที่จะใช้ WebSocketStream API ใช่ไหม การสนับสนุนแบบสาธารณะของคุณช่วยให้ทีม Chrome จัดลำดับความสำคัญของฟีเจอร์ต่างๆ ได้ และแสดงให้เห็นว่าการสนับสนุนฟีเจอร์เหล่านี้สำคัญกับผู้ให้บริการเบราว์เซอร์รายอื่นๆ เพียงใด
ส่งทวีตถึง @ChromiumDev โดยใช้แฮชแท็ก
#WebSocketStream
และแจ้งให้เราทราบว่าคุณใช้ฟีเจอร์นี้ที่ไหนและอย่างไร
ลิงก์ที่มีประโยชน์
- คำอธิบายแบบสาธารณะ
- WebSocketStream API Demo | แหล่งที่มาของ WebSocketStream API Demo
- ข้อบกพร่องการติดตาม
- รายการ ChromeStatus.com
- คอมโพเนนต์ Blink:
Blink>Network>WebSockets
ขอขอบคุณ
Adam Rice และ Yutaka Hirano เป็นผู้ติดตั้งใช้งาน WebSocketStream API