Hello, rustaceans!
I want to create a server with multipurpose sockets (web and classic sockets).
How do this? How do you check the type of connection in runtime?
Why do you want to accept both WebSocket and plain TCP connections from the same port? Generally just having every client use WebSockets should be fine.
You'd have to buffer and then pre-parse the incoming message to see if it matches a WebSocket connection or your plain TCP protocol. It's certainly possible to do but I'm not sure that it would actually be useful. You'll have to maintain separate code paths for every message you receive and reply you send since your plain TCP protocol and the WebSocket protocol would be different[1]
and if they weren't different then you would have just reinvented WebSockets âŠī¸
the desktop app is already. I don't want to modify the desktop app. I create a new server in Rust for it.
I would like to do a possibility for the creation of a future web app.
As I understand, a websocket connection starts with a handshake - an HTTP request/response pair in which the client and server agree to communicate via the websocket protocol. If the client fails to produce a handshake of the appropriate form, you can go ahead with trying to communicate via a "plain" TCP connection (whatever that entails for your application, I'm not sure).
I'm not really familiar with all the libs involved, but this should be fairly straightforward with tungstenite which will accept an abstract stream type.
The only issue is that to fallback to the plain stream correctly, you would need to rewind the stream to the beginning (since the handshake protocol would try to consume some data from the start in order to read the HTTP request). If your stream is seekable you can try something like (untested)
enum MyStream<S>
{
Websocket(WebSocketStream<S>),
Plain(S)
}
async fn get_connection_type(tls_stream: S) -> MyStream<S>
{
accept_async(tls_stream).await
.map(|s| MyStream::Websocket(s))
.unwrap_or_else(|_| {
s.rewind(); // pretend we never read from the stream
MyStream::Plain(s)
})
}
If not you would probably have to wrap the stream type with your own stream which saves the first X MB of the stream and provides a method like rewind.
bind syscall is always blocking anyway. TcpListener::bind is async because it may have to resolve DNS hostname. But this is not your case, so just make the two binds one after the other is not less performant.
But, if you want to call these concurrently, you could use either tokio::select! or spawn two tasks. Depending on what you will do, one or the other may be more adapted.
The simplest is still to do it sequentially and move them to different tasks if the need for separate tasks occurs.
Note that WebSocketStream is already Stream + Sink, you wouldn't need a codec for it.
The difference is that it is a stream over websocket messages and not bytes. You have to choose how to convert these into bytes (you could only extra bytes from data messages and ignore other messages for example, but it really depends on what you want). You can use map or filter_map on WebSocketStream for this.
This will be a different type than Framed. You'll have to store it in a Box<dyn Stream + Sink> or use a custom enum type as you did with MyStream.