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

Ngăn ứng dụng của bạn bị tràn thông báo WebSocket hoặc làm tràn thông báo trên máy chủ WebSocket bằng cách áp dụng nền.

Thông tin khái quát

API WebSocket

API WebSocket cung cấp giao diện JavaScript cho giao thức WebSocket, giúp mở ra 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 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

API Luồng cho phép JavaScript truy cập theo phương thức lập trình vào các luồng phân đoạn dữ liệu nhận được qua mạng và xử lý chúng như mong muốn. Khi nói đến luồng, một khái niệm quan trọng là hậu áp. Đây là quá trình trong đó một luồng hoặc một chuỗi ống điều chỉnh tốc độ đọc hoặc ghi. Khi bản thân luồng hoặc một luồng sau đó trong chuỗi luồng vẫn đang bận và chưa sẵn sàng chấp nhận nhiều đoạn hơn, nó sẽ gửi tín hiệu ngược thông 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 ngược áp lực cho thư đã nhận

Với API WebSocket hiện tại, việc phản ứng với một thông báo diễn ra trong WebSocket.onmessage! EventHandler được gọi khi nhận được tin nhắn 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 bất cứ khi nào nhận được tin nhắn mới. Bạn có thể thiết lập quy trình tương tự như mã bên dưới, và vì bạn là await là kết quả của lệnh gọi process() nên giờ bạn nói đúng chứ?

// 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 nền. Khi thư đến nhanh hơn khả năng xử lý của phương thức process(), 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

Có thể áp dụng áp lực ngược cho thư đã gửi, nhưng sẽ phải thăm dò WebSocket.bufferedAmount là một thuộc tính không hiệu quả và phi công thái học. Thuộc tính chỉ đọc này trả về số byte dữ liệu đã được đưa vào hàng đợi sử dụng cuộc gọi đến WebSocket.send()! nhưng chưa được truyền đến mạng. Giá trị này đượ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(), nó sẽ tiếp tục tăng.

WebSocketStream API là gì?

API WebSocketStream giải quyết vấn đề áp lực không tồn tại hoặc không công thái học 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 "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

Ví dụ về các trang web có thể sử dụng API này bao gồm:

  • 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ự, 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 thông báo giải thích Hoàn tất
2. Tạo bản nháp ban đầu của thông số kỹ thuật Đ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 Not started

Cách sử dụng API WebSocketStream

Ví dụ về giới thiệu

API WebSocketStream dựa trên hứa hẹn, giúp việc xử lý trở nên 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 đợi kết nối được opened, dẫn đến ReadableStream và/hoặc WritableStream.

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

Theo đó, bằng cách gọi WritableStream.getWriter() Cuối cùng, bạn sẽ có được WritableStreamDefaultWriter, mà sau đó bạn có thể write() dữ liệu đến.

  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? Như tôi đã viết ở trên, bạn sẽ nhận được sản phẩm "miễn phí" mà 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 sẽ chỉ được sử dụng sau khi quy trình đã sẵn sàng. Tương tự như bước WritableStreamDefaultWriter.write() sẽ 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. Hiện tại, 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 cung cấp thông qua WebSocketStream.opened Cam kết. Tất cả thông tin về kết nối trực tiếp đều được cung cấp theo lời hứa này, vì sẽ không liên quan nếu không kết nối được.

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ừ WebSocket.oncloseWebSocket.onerror sự kiện trong API WebSocket hiện được cung cấp thông qua lời hứa WebSocketStream.closed. Lời hứa sẽ bị từ chối trong trường hợp kết thúc giao dịch không rõ ràng, nếu không thì nó 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 chúng đượ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

Có thể đóng WebSocketStream bằng AbortController. Do đó, hãy chuyể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 nó là cho phép chỉ định và lý do 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. Để có khả năng tương tác với API WebSocket cổ điển, không thể áp dụng áp lực ngược cho thư đã nhận. Có thể áp dụng áp lực ngược cho thư đã gửi, nhưng sẽ phải thăm dò WebSocket.bufferedAmount là một thuộc tính không hiệu quả và phi công thái học.

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ể thấy WebSocketStream API đ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

Có điều gì về API không hoạt động như bạn mong đợi không? Hay có phương thức hay thuộc tính nào bị thiếu để triển khai ý tưởng của bạn 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? Báo cáo 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 về vấn đề hiện có.

Báo cáo sự cố về triển khai

Bạn có phát hiện lỗi trong quá trình 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? Báo cáo 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 tạo, rồi 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 mô phỏng 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ự hỗ trợ 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 biết tầm quan trọng của việc hỗ trợ họ.

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

Các đường liên kết hữu ích

Xác nhận

API WebSocketStream do Adam gạo triển khai và Yutaka Hirano. Hình ảnh chính của Daan Mooij đang bật Không hiển thị màn hình.