Rocket: Support for Server Sent Events

Created on 24 Dec 2016  路  16Comments  路  Source: SergioBenitez/Rocket

It looks like the streams could be used to implement Server Sent Events, but it would be nice if there was some built in support.

accepted request

Most helpful comment

@SergioBenitez There are a number of differences between Server Send Events (SSE) and WebSockets (WS) -- definitely worth reading up on. They both have value for their specific use-cases. SSE has some great features, and WS has some problem that only become apparent once you investigate them. For example, WS don't currently work with AWS ELBs unless the ELB is in PROXY-mode, which removes a lot of the benefit of an ELB! Also, the security model for WS is different than standard HTTP, which complicates securely implementing WS. WS need an UPGRADE on the HTTP protocol to work, whereas SSE does not. There are a number of other advantages to SSE (easy to implement, browsers automatically reconnect if they become disconnected, ... more ... more), and are good for when you don't have to have the bi-directional capabilities or super-low-latency of WS. I would definitely vote +1 for SSE support in Rocket (which, I'm still loving!)

All 16 comments

Unless otherwise stated, everything is streaming in Rocket by default, not just Stream. If you'd like, you can implement server sent events via Responder. You'd need to create a new Read type that does the appropriate thing. Of course, that Responder will block while sending events, a side effect of Rocket being synchronous. This will eventually stop being the case.

On a more fundamental level: why would someone use server-sent events instead of web sockets? I think seeing some kind of native support for web sockets would be nice, and it would be even nicer if that supplanted the need for server-sent events.

why would someone use server-sent events instead of web sockets

Because if you only need unidirectional (server to browser) notifications, why set up the more complex WebSocket protocol? Okay, I know WS is not actually complex, but still. SSE just feels lighter.

+1 on the native support for web sockets within Rocket

@SergioBenitez There are a number of differences between Server Send Events (SSE) and WebSockets (WS) -- definitely worth reading up on. They both have value for their specific use-cases. SSE has some great features, and WS has some problem that only become apparent once you investigate them. For example, WS don't currently work with AWS ELBs unless the ELB is in PROXY-mode, which removes a lot of the benefit of an ELB! Also, the security model for WS is different than standard HTTP, which complicates securely implementing WS. WS need an UPGRADE on the HTTP protocol to work, whereas SSE does not. There are a number of other advantages to SSE (easy to implement, browsers automatically reconnect if they become disconnected, ... more ... more), and are good for when you don't have to have the bi-directional capabilities or super-low-latency of WS. I would definitely vote +1 for SSE support in Rocket (which, I'm still loving!)

This would be great. I tried to make my own implementation using a Responder but my rust knowledge is not there yet. I would love to see an example of how to do SSE in Rocket.

I believe Rocket should be able to handle Server Sent Events. It's likely that the implementation would reside in contrib, but core should make such an implementation as simple as possible. That being said, the current synchronous architecture isn't amenable to SSE. As a result, I'm slating this for a later milestone. Nonetheless, I am committed to seeing this in Rocket.

Hello, i am having (almost) the same need.

As i am a complete beginner with rust (im trying to see how hard it would be to port an existing project from C to Rust), but i still have been able to come up with this code :
https://gist.github.com/gfriloux/5f82c702e445fdc4b447586bb6721dbd

What it does is simply read on a unix pipe.
If i only return a File structure from the pending function, rocket.rs will only read the pipe once and return this result to the HTTP client (so it wont be a "stream" or a chunked answer).

So i create a PipeLoop structure, implement Responder and Read for it, and each read call will be blocking.
This allows any other app to write into the pipe as it has things to write, and rocket.rs to be aware of it.

Still, there is a thing that doesnt work properlly : rocket.rs will bufferise on its side.
It seems to wait for around 16KB of data to start sending the first bytes.

For example, if my pipe is /tmp/testfifo, i have to do this :

# echo "toto" >/tmp/testfifo
# perl -E 'say "=" x 8000' >/tmp/testfifo
# perl -E 'say "=" x 8000' >/tmp/testfifo

Only to receive "toto" on the client side.

This is where i begin to not know what to do.
I am reading rocket's source code, trying to find an answer, but if you know the last bits needed to get it working, it would be awesome.

I'd like to come up with a workaround before v0.8.0 (having a long running thread isn't a problem in my case).

Using chunked_body() (with a size of 5 bytes for example) will trigger a lot of read() calls into my PipeLoop structure, but it won't send anything until i pile thousands of chars.

This makes me believe (i am not yet able to determine it) that hyper will bufferize on its side.

Using Stream instead of writing my own Responder makes the code more simple, but gives the same result.

Here is a code example : https://gist.github.com/gfriloux/c8846afed6d2bfceffd13fc5375bced3

I dont know where its blocking (my low rust knowledge doesnt help) but i assume it should not wait to have the buffer filled to send data.

Data should be sent either immediately or after a very short window.
This would unblock this issue the time you implement SSE inside rocket.rs.

Comparing to C functions, write() or send() won't block data until a fixed amount of data is given.
You can send 1 byte without any problem, it won't delay it indefinitly, waiting for more data.

Ok, after spending some days on other stuff, im back at this.
The problem isnt in rocket.rs itself.

setting logs at debug level, i can see that hyper writes the data, but the HTTP client doesnt get it :

--> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.10.13/src/server/response.rs:220
write 10 bytes
chunked write, size = 10

You need to write a bunch of data for it to be received by the HTTP client (it wont even send the HTTP headers, in some cases).
As i had doubts about curl, i directly went with telnet, and the same problem occurs.
My first hint is that hyper is missing a few flushes.

So i will stop reporting here this behavior.

Ok, I managed to patch version 0.10.13 of hyper to force flush sockets.
I know i said i would stop posting here, but this may help more people than me.
This enables to do streaming "properly", so SSE and that kind of stuff.
Latest Hyper versions does use flush, but rocket.rs can't build with latest hyper due to various changes.

Basically, for hyper 0.10.13, edit src/server/response.rs and you need to have :

217 impl<'a> Write for Response<'a, Streaming> {
218     #[inline]
219     fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
220         debug!("write {:?} bytes", msg.len());
221         let r = self.body.write(msg);
222         self.body.flush();
223         r
224     }

This will properlly flush the socket and make it possible to send only a few bytes.
The sample codes i pasted above will then work perfectly well.

In my opinion it would be great to have an example that shows how to currently implement a simple SSE or websocket solution using the current API (I guess using Stream in some way).

The problem with an example of SSE in the current API is related to this point:

Of course, that Responder will block while sending events, a side effect of Rocket being synchronous

"will block" has the serious consequence that the maximum number of clients with open SSE channels is bounded by the number of worker threads, at which point the server will "hang" until those clients disconnect. If I'm reading https://github.com/SergioBenitez/Rocket/issues/33#issuecomment-352058927 correctly, then hyper also needs modifications for SSE to work correctly.

1065 will make doing this not-dangerous - open Response channels "waiting" on the server to push data will no longer prevent new incoming requests from being processed. And #1066 will make SSE easier to implement by eliminating the need to implement some kind of custom Read type.

I think my MR #1365 would satisfy this request for Rocket 0.4.x.

Having read the discussion, I wanted to respond to a few points:

"will block" has the serious consequence that the maximum number of clients with open SSE channels is bounded by the number of worker threads

Yes. Depending on the application, it will be necessary to significantly increase the number of worker threads (and hope the system doesn't overload or get DOS'd). Also there is a browser limitation, see https://developer.mozilla.org/en-US/docs/Web/API/EventSource for which traditional workaround is to make the SSE requests to a wildcard domain with a randomly-chosen leafname, or similar (the client maximum connections limit being per domain, not per IP address).

Ok, I managed to patch version 0.10.13 of hyper to force flush sockets.

I don't think that change is necessary (everything works for me with hyper 0.10.16) or desirable. If rocket can be persuaded to complete a chunk, and issue a flush call, hyper seems to do the right things.

In my opinion it would be great to have an example that shows how to currently implement a simple SSE

I have code that could easily become such a thing if my MR #1365 is accepted. If there is interest I could produce a suitable git branch.

Pushing this up to 0.5.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

incker picture incker  路  3Comments

GoRustafari picture GoRustafari  路  3Comments

haheute picture haheute  路  4Comments

lambda-fairy picture lambda-fairy  路  4Comments

klnusbaum picture klnusbaum  路  4Comments