Skip to content

Memory-friendly behaviour for sending and receiving data on WebSocket #1165

@acarstoiu

Description

@acarstoiu

In the current implementation of the WebSocket protocol, the server can send or receive entire messages only, which is a pitty considering the consumption of memory caused by assembling those byte arrays in full just to deserialize or serialize JS objects from/into them. Multiply this by the number of concurrent connections, which should be in the thousands.

Also consider the case where people need to send fair amounts of data (like files) on the connection. Yes, there are other protocols designed for file transfer, but WebSocket is more versatile 😉

My proposal is to bring streaming behaviour to this package by adding API for sending and receiving messages by fragments:

  1. A new sendByFragments method on WebSocket which parallels the existing send:
    sendByFragments(message: AsyncIterable<RecognizedString>, isBinary?: boolean, compress?: boolean) : Promise<number>,
    along with an enhancement to the current send to also accept an Iterable<RecognizedString> as message
    send(message: RecognizedString|Iterable<RecognizedString>, isBinary?: boolean, compress?: boolean) : number,
    for the case when the serialized data is already loaded in memory (needs not be fetched from some storage).
    👉 Mind that some RecognizedStrings are Iterable, so the fragmented variant should be executed when the message parameter is not a RecognizedString.
    In both variants iteration should be suspended when the message fragment is dropped due to backpressure, by calling return() on the iterator (call it without any guard, so as to crash when return() isn't defined). This is in line with the behaviour of a ReadableStream's asynchronous iterator.
  2. A new messageFragments property on WebSocketBehavior, mutually exclusive with message:
    messageFragments?: (ws: WebSocket<UserData>, isBinary: boolean) => ((fragment: ArrayBuffer, isLast: boolean) => void | Promise<void>))
    It returns a handler for message fragments which µWebSockets.js can call for every received fragment of a message (with the first call expected immediately after this method returns).

The fragments of a WebSocket message need not reflect the boundaries of frames that make up that message (you can have continuation frames after a message frame). The relationship between fragmentation and WebSocket frames should be decided internally by your implementation, perhaps steered by a new configuration value specified in WebSocketBehavior.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions