Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor WebSockets support #547

Open
Tracked by #546
rdeago opened this issue Feb 28, 2022 · 0 comments
Open
Tracked by #546

Refactor WebSockets support #547

rdeago opened this issue Feb 28, 2022 · 0 comments
Assignees
Labels
area:websockets Issue with WebSocket protocol breaking Requires one or more breaking changes. pinned Pinned issues are not closed automatically when stale. RFC Issue that is a request for comments (discussion about specs) v4.x

Comments

@rdeago
Copy link
Collaborator

rdeago commented Feb 28, 2022

Part of the ongoing work on EmbedIO v4.0, as documented in #546, is a near-complete refactor of WebSockets support.

Easier reception callbacks with automatic edge-case handling

Up to EmbedIO v3, in order to subclass WebSocketModule you had to override this method:

/// <summary>
/// Called when this WebSocket server receives a full message (EndOfMessage) from a client.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="result">The result.</param>
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
protected abstract Task OnMessageReceivedAsync(IWebSocketContext context, byte[] buffer, IWebSocketReceiveResult result);

In the overridden method, first you would first have to assess the type of data received. Then, in the case of textual data, you had to convert buffer to a string. Furthermore, the result parameter was quite confusing: apparently you could receive a partial message, or even a close request. OK, you couldn't actually, but then why did you need an IWebSocketReceiveResult interface where a bool (or an enum with two possible values) was enough?

Now you have to override one or both of these methods instead:

/// <summary>
/// Called when this WebSocket server receives a full message (EndOfMessage) from a client.
/// </summary>
/// <param name="context">An instance of <see cref="IWebSocketContext"/>.</param>
/// <param name="text">The text contained in the message.</param>
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
protected virtual Task OnTextMessageReceivedAsync(IWebSocketContext context, string text)
    => throw new WebSocketException(CloseStatusCode.UnsupportedData, "This endpoint does not accept text messages.");

/// <summary>
/// Called when this WebSocket server receives a full message (EndOfMessage) from a client.
/// </summary>
/// <param name="context">An instance of <see cref="IWebSocketContext"/>.</param>
/// <param name="data">The binary data contained in the message.</param>
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
protected virtual Task OnBinaryMessageReceivedAsync(IWebSocketContext context, byte[] data)
    => throw new WebSocketException(CloseStatusCode.UnsupportedData, "This endpoint does not accept binary messages.");

Textual data is converted to string for you; the connection is closed with an appropriate status code, as per RFC6455, if the received data is not valid UTF-8 text.

If you override only one of the two methods above, the reception of a kind of data you don't expect causes the connection to be closed, as per RFC6455, with a status code of 1003 (the value of CloseStatusCode.UnsupportedData).

The "frame" reception callback has been removed

Method WebSocketModule.OnFrameReceivedAsync has been removed because it complicated message reception code to support a feature that hardly any application needs. WebSocket applications should never rely on internals details of the protocol implementation anyway: the unit of data exchanged on a WebSocket is the message, not the frame.

In addition, OnFrameReceivedAsync had never worked as advertised anyway when using HttpListenerMode.Microsoft. Their WebSocket class does not yield single frames; instead, it aggregates them into complete messages and may then yield partial messages according to the size of the buffer allocated for reception.

WebSocketException now goes both ways

WebSocketException used to be thrown by EmbedIO to signal an anomalous condition or the attempt to perform an operation on a WebSocket that was not supported (typically writing to a closed socket).

Now you may also throw a WebSocketException from a callback method (OnBinaryMessageReceivedAsync, OnTextMessageReceivedAsync, OnClientConnectedAsync) to command the connection to be closed. This lets you write simpler code, as previously you had to call Close and return.

Internal changes

Code has been optimized to perform as few allocations as possible.

Support for data compression in WebSockets (not part of RFC6455) has been removed. It had never been enabled anyway.

The message reception loop has been unified between HTTP listener modes. One consequence is the creation of one less parallel Task for each WebSocket connection.


Comments are very welcome. Expect to be able to try the modified WebSocket support in v4.0-preview.1 soon.

@rdeago rdeago added breaking Requires one or more breaking changes. area:websockets Issue with WebSocket protocol v4.x pinned Pinned issues are not closed automatically when stale. RFC Issue that is a request for comments (discussion about specs) labels Feb 28, 2022
@rdeago rdeago self-assigned this Feb 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:websockets Issue with WebSocket protocol breaking Requires one or more breaking changes. pinned Pinned issues are not closed automatically when stale. RFC Issue that is a request for comments (discussion about specs) v4.x
Projects
None yet
Development

No branches or pull requests

1 participant