WebSocketStream: tích hợp luồng với API WebSocket

Ngăn ứng dụng của bạn bị ngập trong các thông báo WebSocket hoặc làm ngập máy chủ WebSocket bằng các thông báo bằng cách áp dụng áp lực ngược.

Thông tin khái quát

WebSocket API cung cấp giao diện JavaScript cho giao thức WebSocket, cho phép mở một phiên giao tiếp tương tác hai chiều giữa trình duyệt của người dùng và máy chủ. Với API này, bạn có thể gửi thông báo đến máy chủ và nhận phản hồi do sự kiện điều khiển mà không cần thăm dò ý kiến máy chủ để nhận thư trả lời.

Streams API

Streams API cho phép JavaScript truy cập vào các luồng dữ liệu nhận được qua mạng theo phương thức lập trình và xử lý các luồng đó theo ý muốn. Một khái niệm quan trọng trong ngữ cảnh của luồng là áp lực ngược. Đây là quy trình mà một luồng hoặc chuỗi ống điều chỉnh tốc độ đọc hoặc ghi. Khi luồng hoặc luồng sau trong chuỗi ống vẫn bận và chưa sẵn sàng chấp nhận thêm các đoạn, luồng đó sẽ gửi tín hiệu ngược lại qua chuỗi để làm chậm quá trình phân phối khi thích hợp.

Vấn đề với API WebSocket hiện tại

Không thể áp dụng áp lực ngược cho các thông báo đã nhận

Với API WebSocket hiện tại, phản ứng với một thông báo sẽ diễn ra trong WebSocket.onmessage, một EventHandler được gọi khi nhận được thông báo từ máy chủ.

Giả sử bạn có một ứng dụng cần thực hiện các thao tác xử lý dữ liệu nặng mỗi khi nhận được một thông báo mới. Bạn có thể thiết lập flow tương tự như mã bên dưới, và vì bạn await kết quả của lệnh gọi process(), nên bạn sẽ ổn, phải không?

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

Sai! Vấn đề với API WebSocket hiện tại là không có cách nào để áp dụng áp lực ngược. Khi các thông báo đến nhanh hơn mức phương thức process() có thể xử lý, quá trình kết xuất sẽ lấp đầy bộ nhớ bằng cách lưu các thông báo đó vào bộ đệm, không phản hồi do mức sử dụng CPU 100% hoặc cả hai.

Việc áp dụng áp lực ngược cho các thông báo đã gửi là không phù hợp

Bạn có thể áp dụng áp lực ngược cho các thông báo đã gửi, nhưng điều này liên quan đến việc thăm dò ý kiến về thuộc tính WebSocket.bufferedAmount. Việc này không hiệu quả và không phù hợp. Thuộc tính chỉ có thể đọc này trả về số byte dữ liệu đã được đưa vào hàng đợi bằng cách sử dụng lệnh gọi đến WebSocket.send() nhưng chưa được truyền đến mạng. Giá trị này sẽ đặt lại về 0 sau khi tất cả dữ liệu trong hàng đợi đã được gửi, nhưng nếu bạn tiếp tục gọi WebSocket.send(), giá trị này sẽ tiếp tục tăng.

WebSocketStream API là gì?

API WebSocketStream giải quyết vấn đề áp lực ngược không tồn tại hoặc không phù hợp bằng cách tích hợp luồng với API WebSocket. Điều này có nghĩa là bạn có thể áp dụng áp lực ngược "miễn phí" mà không mất thêm phí.

Các trường hợp sử dụng được đề xuất cho API WebSocketStream

Sau đây là ví dụ về các trang web có thể sử dụng API này:

  • Các ứng dụng WebSocket băng thông cao cần duy trì khả năng tương tác, đặc biệt là video và chia sẻ màn hình.
  • Tương tự, tính năng quay video và các ứng dụng khác tạo ra nhiều dữ liệu trong trình duyệt cần được tải lên máy chủ. Với áp lực ngược, ứng dụng có thể ngừng tạo dữ liệu thay vì tích luỹ dữ liệu trong bộ nhớ.

Trạng thái hiện tại

Bước Trạng thái
1. Tạo video giải thích Hoàn tất
2. Tạo bản nháp ban đầu của quy cách Đang tiến hành
3. Thu thập ý kiến phản hồi và lặp lại thiết kế Đang tiến hành
4. Bản dùng thử theo nguyên gốc Hoàn tất
5. Khởi chạy Chưa bắt đầu

Cách sử dụng API WebSocketStream

API WebSocketStream dựa trên lời hứa, giúp bạn xử lý API này một cách tự nhiên trong thế giới JavaScript hiện đại. Bạn bắt đầu bằng cách tạo một WebSocketStream mới và truyền URL của máy chủ WebSocket vào đó. Tiếp theo, bạn đợi kết nối là opened, dẫn đến ReadableStream và/hoặc WritableStream.

Bằng cách gọi phương thức ReadableStream.getReader(), cuối cùng bạn sẽ nhận được ReadableStreamDefaultReader, sau đó bạn có thể read() dữ liệu từ đó cho đến khi luồng hoàn tất, tức là cho đến khi luồng trả về một đối tượng ở dạng {value: undefined, done: true}.

Do đó, bằng cách gọi phương thức WritableStream.getWriter(), cuối cùng bạn sẽ nhận được WritableStreamDefaultWriter, sau đó bạn có thể write() dữ liệu.

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

Áp lực ngược

Còn tính năng áp lực ngược đã hứa thì sao? Bạn sẽ nhận được "miễn phí" mà không cần làm gì thêm. Nếu process() mất thêm thời gian, thì thông báo tiếp theo sẽ chỉ được sử dụng sau khi quy trình đã sẵn sàng. Tương tự, bước WritableStreamDefaultWriter.write() chỉ tiếp tục nếu an toàn.

Ví dụ nâng cao

Đối số thứ hai cho WebSocketStream là một túi tuỳ chọn để cho phép mở rộng trong tương lai. Lựa chọn duy nhất là protocols, hoạt động giống như đối số thứ hai cho hàm khởi tạo WebSocket:

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

protocol đã chọn cũng như extensions tiềm năng là một phần của từ điển có sẵn thông qua lời hứa WebSocketStream.opened. Tất cả thông tin về kết nối trực tiếp đều do lời hứa này cung cấp, vì thông tin này không liên quan nếu kết nối không thành công.

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

Thông tin về kết nối WebSocketStream đã đóng

Thông tin có sẵn từ các sự kiện WebSocket.oncloseWebSocket.onerror trong API WebSocket hiện có sẵn thông qua lời hứa WebSocketStream.closed. Lời hứa sẽ từ chối trong trường hợp đóng không sạch, nếu không, lời hứa sẽ phân giải thành mã và lý do do máy chủ gửi.

Tất cả mã trạng thái có thể có và ý nghĩa của các mã này được giải thích trong danh sách mã trạng thái CloseEvent.

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

Đóng kết nối WebSocketStream

Bạn có thể đóng WebSocketStream bằng AbortController. Do đó, hãy truyền AbortSignal vào hàm khởi tạo WebSocketStream.

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

Ngoài ra, bạn cũng có thể sử dụng phương thức WebSocketStream.close(), nhưng mục đích chính của phương thức này là cho phép chỉ định và lý do được gửi đến máy chủ.

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

Cải tiến tăng dần và khả năng tương tác

Chrome hiện là trình duyệt duy nhất triển khai API WebSocketStream. Để tương tác với API WebSocket cổ điển, bạn không thể áp dụng áp lực ngược cho các thông báo đã nhận. Bạn có thể áp dụng áp lực ngược cho các thông báo đã gửi, nhưng điều này liên quan đến việc thăm dò ý kiến về thuộc tính WebSocket.bufferedAmount. Việc này không hiệu quả và không phù hợp.

Phát hiện tính năng

Để kiểm tra xem API WebSocketStream có được hỗ trợ hay không, hãy sử dụng:

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

Bản minh hoạ

Trên các trình duyệt hỗ trợ, bạn có thể xem API WebSocketStream đang hoạt động trong iframe được nhúng hoặc trực tiếp trên Glitch.

Phản hồi

Nhóm Chrome muốn biết trải nghiệm của bạn với API WebSocketStream.

Giới thiệu cho chúng tôi về thiết kế API

API có hoạt động như mong đợi không? Hay có phương thức hoặc thuộc tính nào bị thiếu mà bạn cần để triển khai ý tưởng của mình không? Bạn có câu hỏi hoặc nhận xét về mô hình bảo mật? Gửi vấn đề về thông số kỹ thuật trên kho lưu trữ GitHub tương ứng hoặc thêm ý kiến của bạn vào một vấn đề hiện có.

Báo cáo vấn đề về việc triển khai

Bạn có phát hiện lỗi khi triển khai Chrome không? Hay cách triển khai có khác với thông số kỹ thuật không? Gửi lỗi tại new.crbug.com. Hãy nhớ cung cấp càng nhiều thông tin chi tiết càng tốt, hướng dẫn đơn giản để tái hiện lỗi và nhập Blink>Network>WebSockets vào hộp Components (Thành phần). Glitch rất phù hợp để chia sẻ các trường hợp tái hiện nhanh chóng và dễ dàng.

Hỗ trợ API

Bạn có dự định sử dụng API WebSocketStream không? Sự ủng hộ công khai của bạn giúp nhóm Chrome ưu tiên các tính năng và cho các nhà cung cấp trình duyệt khác thấy tầm quan trọng của việc hỗ trợ các tính năng đó.

Gửi một tweet đến @ChromiumDev bằng hashtag #WebSocketStream và cho chúng tôi biết bạn đang sử dụng ở đâu và như thế nào.

Đường liên kết hữu ích

Lời cảm ơn

WebSocketStream API do Adam RiceYutaka Hirano triển khai.