WebSocketStream: tích hợp các 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.

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 các phản hồi dựa trên sự kiện mà không cần thăm dò máy chủ để nhận phản hồi.

API Luồng

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 bối cảnh của luồng là áp lực ngược. Đây là quá trình một luồng duy nhất hoặc một 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 thu thập dữ liệu nặng bất cứ khi nào nhận được 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 thư đã 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 này trả về số byte dữ liệu đã được đưa vào hàng đợi bằng cách sử dụng các lệnh gọi đến WebSocket.send(), nhưng chưa được truyền đến mạng. Giá trị này sẽ được đặt lại về 0 sau khi tất cả dữ liệu trong hàng đợi đã được gửi đ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ì tính tương tác, cụ thể là chia sẻ video và chia sẻ màn hình.
  • Tương tự như vậy, quay video và các ứng dụng khác tạo 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 rồi truyền vào đó URL của máy chủ WebSocket. Tiếp theo, bạn chờ kết nối là opened, dẫn đến một ReadableStream và/hoặc một WritableStream.

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

Theo đó, bằng cách gọi phương thức WritableStream.getWriter(), cuối cùng bạn sẽ nhận được WritableStreamDefaultWriter mà sau đó bạn có thể write() cập nhật 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 hẹn thì sao? Bạn được sử dụng "miễn phí" và không cần thực hiện thêm bước nào. Nếu process() mất thêm thời gian, thông báo tiếp theo chỉ được sử dụng sau khi quy trình đã sẵn sàng. Tương tự, bước WritableStreamDefaultWriter.write() chỉ tiến hành nếu thấy 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 gia hạn 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 đều có trong từ điển có sẵn thông qua lời hứa WebSocketStream.opened. Lời hứa này cung cấp tất cả thông tin về kết nối trực tiế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ẽ bị từ chối trong trường hợp đóng không đúng cách, nếu không, lời hứa sẽ được 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'});

Khả năng tương tác và cải tiến tiến bộ

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 WebSocketStream API 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 tìm hiểu về trải nghiệm của bạn với API WebSocketStream.

Cho chúng tôi biết 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 này? 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 nhiều thông tin chi tiết nhất có thể, các hướng dẫn đơn giản để tái tạo 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.

Hiện thông tin hỗ trợ về API này

Bạn có định 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 bài đăng đến @ChromiumDev kèm theo hashtag #WebSocketStream và cho chúng tôi biết bạn đang sử dụng hashtag đó ở đâu và bằng cách nào.

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

Xác nhận

API WebSocketStreamYutaka Hirano đã triển khai API WebSocketStream.