Open alexandrehtrb opened 1 year ago
The problem with the idea of a controller is that it requires a way to dispatch messages to a individual actions. WebSockets is like TCP. If you do JSON over websockets then the best we can do is buffer the message and de-serialize it on your behalf. There's no dispatch because the JSON message itself isn't a specific protocol. When you do JSON over websockets, you need to build the dispatching mechanism yourself as there's no standard to do this. An API on the WebSocket type a read a JSON message might be enough for this simple use case though.
Then there's the idea of tracking connections in order to do broadcasts etc. We've already built this, it's called SignalR. It creates a protocol over WebSockets that allows dispatching like a controller. It also creates a model for sending messages to groups of connections.
I read about SignalR and I liked its programming style, and how it decouples the bidirectional communication details.
However, I felt that SignalR is almost a protocol on its own, since it requires specific client-side libraries, not just a regular WebSocket communication. This StackOverflow question is related to this same issue.
If SignalR could act as an agnostic WebSocket server, it would be a solution for this.
Edit: The StackOverflow answer for the question says that SignalR could act like that, but I have not found the documentation for that; is it possible?
I think I clarified that in the previous comment, specifically:
There's no dispatch because the JSON message itself isn't a specific protocol. When you do JSON over websockets, you need to build the dispatching mechanism yourself as there's no standard to do this. An API on the WebSocket type a read a JSON message might be enough for this simple use case though.
Here are some ideas on what could be simplified:
As a strawman:
public abstract class WebSocketHandler
{
public virtual Task OnConnectedAsync();
public virtual Task OnMessageAsync(WebSocketReceiveResult receiveResult);
public virtual Task OnClosedAsync();
}
OR we could embrace a pull model with IAsyncEnumerable<WebSocketReceiveResult>
. Of course there are questions about buffer ownership.
foreach (WebSocketReceiveResult result in webSocket.GetMessagesAsync())
{
// Do something with the message
}
cc @BrennanConroy
I'm not sure how useful WebSocketHandler
would actually be. It's a very thin wrapper around the existing WebSocket
API. The main thing it looks like it would provide is the main loop and calling close async.
while (ws.Status == WebSocketState.Open)
{
await ws.ReceiveAsync(...);
}
await ws.CloseAsync();
The user would still need to handle sending messages separately and closing the connection manually. I'm not sure how that would look with the abstraction since the abstraction tries to move the code to be callback based, but SendAsync
/CloseAsync
wouldn't want to be callback based.
And like you mention, the buffer ownership would likely be inefficient or easy to mishandle because the framework would need to assume the user will use the buffer outside of OnMessageAsync
or assume the user knows they can't use it outside of that method.
- Handling of the individual message buffering
Extension methods on WebSocket
may be able to help here and would apply to both server and client.
e.g. ws.ReadTextMessageAsync()
could read text frames until an end of message flag was received, and then give back something that combines the frames.
And ws.ReadBinaryMessageAsync()
would do the same but for binary messages.
Those APIs would be really inefficient though, as you mentioned, how do we handle buffer ownership in an efficient way? But I think those APIs are a good potential path towards simplifying WebSocket consumption logic.
IAsyncEnumerable<WebSocketReceiveResult>
This is also interesting, it still leaves the user in control of the logic, but would handle combining frames.
https://devblogs.microsoft.com/dotnet/announcing-grpc-json-transcoding-for-dotnet
may be this is what author want , signalr(websocket) -json-transcoding-for-dotnet
There are two main points that I think could be improved:
1) The existing WebSocket
and ClientWebSocket
classes are quite difficult to work with if the programmer wants to do bidirectional communication. Taking from this example, if I understood it correctly, the code has to start both sending and receiving processes at the same time, check which comes first, then complete it; pseudo-code below:
while (true)
{
var beganSending = HasMessageToSendAsync();
var beganReceiving = HasMessageToReceiveAsync();
var first = await Task.WhenAny(beganSending, beganReceiving);
if (first == beganSending)
await SendMessageAsync();
else
await ReceiveMessageAsync();
}
An event-based approach would be easier and simpler. Check the JavaScript WebSocket API, for this example:
socket = new WebSocket(connectionUrl.value);
socket.onopen = function (event) {
updateState();
/*code*/
};
socket.onclose = function (event) {
updateState();
/*code*/
};
socket.onerror = updateState;
socket.onmessage = function (event) {
/*code*/
};
2) In ASP.NET Endpoints and Actions, the code returns an object that is automatically converted to a JSON and then to a byte array, sent through the connection. In current WebSocket classes, however, the programmer has to do all that - accumulate the received bytes, convert them to string, then convert this string to an object. That is quite cumbersome.
The existing WebSocket and ClientWebSocket classes are quite difficult to work with if the programmer wants to do bidirectional communication. Taking from this example, if I understood it correctly, the code has to start both sending and receiving processes at the same time, check which comes first, then complete it; pseudo-code below:
This example is more complex because we're introducing another layer of abstraction (the PipeReader/PipeWriter) instead of directly interacting with the WebSocket APIs. Notice in your examples the focus is on receiving, I don't see the same complaints about sending messages.
See this for the reason we're not a fan of the websocket APIs in the browser.
In ASP.NET Endpoints and Actions, the code returns an object that is automatically converted to a JSON and then to a byte array, sent through the connection.
This is easy because HTTP has 2 parts that are used separately, the path for routing (dispatch) and the body for the payload. We don't have the same with websockets, you only have the body.
In current WebSocket classes, however, the programmer has to do all that - accumulate the received bytes, convert them to string, then convert this string to an object. That is quite cumbersome.
byte[] -> object, there's no need to make a string even though you see lots of samples inefficiently doing that.
There are definitely some small things we can do here to improve the experience, the current API is very low level. However, I think this small improvement should be done for both client and server side websockets.
ASP.NET Core already has a higher-level programming model on top of any transport including WebSockets. I still think we should focus on APIs that aren't callback based that accomplish the following:
Out of scope:
@davidfowl Should we open an issue to track this:
I still think we should focus on APIs that aren't callback based that accomplish the following:
- Handling of the closing sequence
Handling of the individual message buffering
- Translation of that message into a JSON object (of the user's choice)
Yes but I'm hoping we can make this an API proposal on the WebSocket API itself and avoid doing something on the server that doesnt' work on the client.
Tagging subscribers to this area: @dotnet/ncl See info in area-owners.md if you want to be subscribed.
Author: | alexandrehtrb |
---|---|
Assignees: | - |
Labels: | `api-suggestion`, `area-System.Net`, `untriaged` |
Milestone: | - |
What exactly do you understand by "Handling of the closing sequence" @davidfowl?
@CarnaViire when close is received, sending the close frame back.
Triage: easier APIs for WebSockets make sense, so we are open to adding them. For receiving, we should also add some setting like HttpClient.MaxResponseContentBufferSize
to be able to limit the size of a reconstructed message.
Doesn't seem to be critical for 8.0, moving to Future.
If you need this feature, please upvote the top post, it will help us prioritize. Thanks!
cc @vicancy
I would say this is an extremely important feature and could have huge benefits for customers using Azure Web PubSub service.
Azure Web PubSub service supports WebSocket clients connecting to the service directly.
However current native WebSocket APIs are a little bit complex for our customers, especially when compared with other programming languages, and we have to use some third-party WebSocket client packages in our C# samples to simplify the code.
It would be great if we have easier APIs for WebSocket in .NET world.
A bit late to the (old) party ... but commenting on the second comment:
There's no dispatch because the JSON message itself isn't a specific protocol. When you do JSON over websockets, you need to build the dispatching mechanism yourself as there's no standard to do this.
I know one such standard and that is JSON-RPC. And WebSocket is actually an excellent transport for JSON-RPC as it has a message frame that TCP doesn't have. It saves you the JSON object splitting. I think this would be what the requester wanted. Other languages/frameworks like Flask/Python support this, albeit through extension packages (comparable to NuGet).
Personally I find SignalR too complex, for anything I can imagine. I feel like it was once built around a very specific need that nobody knows anymore. It has more features than a simple WebSocket dispatcher (hubs and groups), but at the same time makes simple WebSocket dispatching overly complex. And the library's code size is insane for what it does! Even a very basic solution like JSON-RPC would be easier here. I've also created my own solution that does PubSub+RPC over WebSocket for C# (router and client as NuGet package) and JavaScript (client only), in a simple and straightforward way with less code (currently not open-source yet).
@ygoe There are lots of JSON RPC packages available on NuGet https://www.nuget.org/packages?q=jsonrpc. I'm sure many of them work with ASP.NET Core. You can use one of those instead of SignalR if you don't need any of the features it offers.
Is there an existing issue for this?
Is your feature request related to a problem? Please describe the problem.
I recently began to learn WebSockets and the idea of bidirectional communication over HTTP, and I am liking it. I read the some examples online, such as this and managed to make a test server, using ASP.NET Core.
I noticed that writing code for WebSockets, at least in ASP.NET Core, is very "close to the metal". The developer needs to write code for receiving and sending the messages, translating them from byte arrays, worry about timeouts, and other considerations.
ASP.NET Core could have a friendlier programming approach to WebSockets, like Actions in Controllers, that exist for HTTP.
Describe the solution you'd like
I do not have a built idea, but I thought on an abstract class, like a "WebSocketController", with three abstract methods, for connecting, and for sending and receiving messages.
The send message method could return objects, that are converted to JSON strings and then to byte arrays, sent through the WebSocket. This conversion from object to JSON byte array would be done by the ASP.NET, taking this responsibility away from the developer.
The receive message method could have as parameters objects that are already pre-converted from the JSON received in the WebSocket message.
Additional context
No response