Rocket
Rocket copied to clipboard
Native WebSocket support
I think Rocket would benefit heavily from native WebSocket support. Even better would be something similar to Phoenix's channels.
It's great to see such a comprehensive web framework for Rust coming together!
@net did you manage to combine rocket
with eg. websocket on the same host:port
?
I'm trying to upgrade
connection to ws
, but no luck so far :(
This is indeed planned.
@SergioBenitez got any suggestions for how one could manually add web socket support?
@wagenet Do you mean to Rocket or to your web application? The former is subtle is it requires a lot of design work, but the latter should be straightforward. You should be able to use the websocket
or ws-rs
crates as they are intended. You won't be able to use the same port for both servers, of course. And, you'll need to spawn one of the two servers in a separate thread so that one server doesn't block the other one from starting. Should you choose to do this, your main
function would look something like:
fn main() {
thread::spawn(|| {
listen("127.0.0.1:3012", |out| {
move |msg| {
out.send(msg)
}
})
})
rocket.ignite()..more()..launch()
}
@SergioBenitez thanks :) I ended up discovering this sort of approach myself.
@SergioBenitez is this waiting on a future version of Rocket that migrates to Actix or some other async framework?
Is there no way to add web socket support on the same port as HTTP?
If not, this is quite problematic for me.
@sparky8251 Not currently, at least easily.
Well, that sucks. Liked the readability of Rocket but without the ability to have websockets on the same port Rocket is useless to me.
Actix just doesnt have the same readability after all...
If you happen to know a way to do websockets on the same port, even if its not clean, I'd love to hear it. If I end up porting to Actix I won't be returning to Rocket but if I can make the websocket stuff work, hopefully I can stick around until native support lands.
@sparky8251 nginx ("Since version 1.3.13") should be able to forward to a second server on a per-location basis. The second server could even be another listening port connected to the same binary that's serving the rocket HTTP server (e.g. via https://github.com/SergioBenitez/Rocket/issues/90#issuecomment-304725055).
I've made an example (completely untested so far, sorry) based on the nginx example for WebSocket:
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80 default server;
server_name example.com;
location /websocket/ {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location / {
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
I presume something similar can be done with Apache as well.
Unfortunately, using nginx is a non-answer for my use case. I'd like to not need a 3rd party program to make this work.
Hyper 0.12 supports websockets: https://github.com/hyperium/hyper/blob/master/examples/upgrades.rs
Rocket is still on hyper 0.10 though, so we'll need to upgrade that dependency first
Edit: which seems blocked on 0.5.0: https://github.com/SergioBenitez/Rocket/issues/17#issuecomment-415609486
With #1008 merged, would it make sense to start implementing websocket support on the async branch?
I think we would need to see some progress on #1066 first - which reminds me, I need to write a bit more about that. And WebSocket support is also big enough that I'd like to see a hypothetical API design before going too far into implementation.
WebSockets will certainly be one of the larger issues. I'm actually looking into the two main websocket crates (ws
and websocket
). ws
is, frankly, a bit of a mess, and will be extremely difficult to upgrade to asynchronous. websocket
already has support, though it's running on tokio 0.1 and hyper 0.10. I've just asked on an issue there to see what sort of changes will be accepted.
While this wouldn't directly add websocket support to Rocket, it would allow for an asynchronous server that could be spawned on the same runtime.
Actually, I don't think #1066 is necessary for this after all - it would help with SSE, but not websockets. Instead, websockets would likely have to be handled at the hyper-service or connection level with an API such as https://docs.rs/websocket/0.23.0/websocket/server/upgrade/async/trait.IntoWs.html.
@jebrosen, based on your async
branch I created a branch with rudimentary websocket support and I would like to contribute this new feature to rocket.
The first draft of the API looks likes this:
let (tx, rx) = channel();
std::thread::spawn(move || {
let duration = std::time::Duration::from_secs(1);
loop {
println!("Sending message");
tx.unbounded_send(Message{}).unwrap();
std::thread::sleep(duration);
}
});
let _ = rocket::ignite().receivers(vec![rx]).mount("/", routes![hello]).launch();
@SergioBenitez, @jebrosen, what do you think about this first implementation.
BTW: here is the branch, implementing the behavior.
My vague idea for a websocket API was to be able to do something like the following:
#[websocket("/chat/<room>")]
async fn chat(room: String, conn: WebSocket) {
// let message = conn.recv().await;
// conn.send(message).await;
}
fn main() {
rocket::ignite().websocket(websocket![chat]);
}
That is, have it work similarly and in parallel to catcher
s and route
s. It should also have a lower-level API like Handler
. I'm not sure if we should support only Websocket or expose more flexibility in terms of other protocols via Upgrade
.
As far as I can tell your current implementation takes any number of receivers, and every message that comes on any receiver is sent to all connected clients - and that pattern is too limiting.
Okay, that looks much nicer, but I have one question. If I understand your example correctly, wouldn't this require that Rocket run the chat
function every x
milliseconds?
As far as I can tell your current implementation takes any number of receivers, and every message that comes on any receiver is sent to all connected clients - and that pattern is too limiting.
This is not intended to be the final result of a future PR. It was just a very basic implementation to get it running.
Currently, I'm wondering if we could combine our API designs:
#[websocket("/chat/<room>")]
async fn chat(room: String, msg: Message) -> Json<Value> {
// Will be invoced when rx emits a message and here you could
// transform the message into a certain paylod.
}
fn main() {
let (tx, rx) = channel();
std::thread::spawn(move || {
let duration = std::time::Duration::from_secs(1);
loop {
println!("Sending message");
// Message could be a trait and here you could create a
// text message (similar to websockets crate API)
tx.unbounded_send(Message{}).unwrap();
std::thread::sleep(duration);
}
});
rocket::ignite().websocket(vec![websocket!(chat, rx)]);
}
This API still seems very limited, since it wouldn't be able to send messages on its own, or respond to a message with multiple messages. There's also a lot of boilerplate in main
, which seems like it would be unnecessary. A websocket handler that continues to handle the websocket connection for its entire lifetime, as in @jebrosen's example, would be ideal.
I'm a bit confused how you would receive message from a background process (imaging that you are receiving Kafka messages from another service in the background and want to update the UI through a websocket connection). Could you give me an example API?
It's hard to give a code example without knowing what the message producer API looks like, but it would probably involve State
.
I previously wrote a chat room example with async rocket and SSE, where the "receiver" endpoint looks like this: https://git.jebrosen.com/jeb/rocket-rooms/src/commit/381f93daaf17db1a430760f536ceb9699c33b2ea/src/main.rs#L34
The websocket equivalent could end up looking something like this:
#[websocket("/room/<room>")]
async fn chat_room(room: String, rooms: State<'_, Rooms<Message>>, socket: WebSocket) {
// Subscribe to the room. 'subscription' is a Stream of Messages.
let mut subscription = rooms.subscribe(&room).await;
// WebSockets are full duplex, and we want to read/write independently
let (tx, rx) = socket.split();
loop {
// TODO: I have no idea if select! actually works like this
futures::select! {
// New message from another client: send it to this one
server_msg = subscription => tx.send(server_msg).await,
// New message from this client: broadcast it to the others
client_msg = rx => rooms.send(&room, client_msg).await,
// TODO: error handling, shutdown, etc.
}
}
}
The main thing this showcases is that neither sending nor receiving of messages is directly controlled by rocket, so the handler can receive from any source.
That example is the example I was looking for in order to understand what should be accomplished. At the weekend I'll have a look into it.
any news about Websocket support with rocket ?
@jebrosen
Can you say anything to compare:
- The work you did
- The work shown at: https://www.steadylearner.com/blog/read/How-to-start-Rust-Chat-App
- What are the cons/pros of native rocket support vs. the two above hybrid approaches?
- using Warp instead of Rocket (Warp has websockets)
@DrYSG
- I haven't done any actual work on websockets, other than provide an example for nginx that I have not tested myself. The purpose of that example is to map servers on two ports to look like they are on the same port.
- That example starts a websocket and a webserver app in the same process listening on two ports. It does not look like the two share data in any way; if needed, the two could communicate through some shared
Arc<Mutex<State>>
or channel(s). - Native rocket support would mean that both HTTP and websocket would truly be handled by the same server listening on the same port, and it would simplify sharing state: the websocket routes could access the same managed state through the same mechanism as non-websocket routes.
- Warp's websocket support is the same kind as number 3 - the same server handles both plain and websocket requests.
see https://github.com/SergioBenitez/Rocket/issues/1238, the autobahn websocket test suite should be included as part of this effort
I worked on native websocket support for Rocket (see my comments above). However, I did not had time to continue because I think it requires large extensions and maybe refactorings of Rocket (maybe a second kind of handler, as I tried to use here). Maybe, we could discuss this here?
@rustrust, using a test suite would very beneficial. :+1:
Is there any update on when this is going to release? In a dire need for WebSocket endpoints.