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

Support for Server Sent Events #33

Closed
rajsite opened this issue Dec 24, 2016 · 22 comments
Closed

Support for Server Sent Events #33

rajsite opened this issue Dec 24, 2016 · 22 comments
Labels
accepted An accepted request or suggestion request Request for new functionality
Milestone

Comments

@rajsite
Copy link

rajsite commented Dec 24, 2016

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.

@SergioBenitez SergioBenitez added the request Request for new functionality label Dec 24, 2016
@SergioBenitez
Copy link
Member

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.

@valpackett
Copy link

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.

@bradleybeddoes
Copy link

+1 on the native support for web sockets within Rocket

@greglearns
Copy link
Contributor

@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!)

@sporto
Copy link

sporto commented Mar 17, 2017

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.

@SergioBenitez
Copy link
Member

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.

@SergioBenitez SergioBenitez added the accepted An accepted request or suggestion label Apr 13, 2017
@SergioBenitez SergioBenitez added this to the 0.8.0 milestone Apr 13, 2017
@gfriloux
Copy link

gfriloux commented Dec 12, 2017

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).

@gfriloux
Copy link

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.

@gfriloux
Copy link

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

@gfriloux
Copy link

gfriloux commented Dec 12, 2017

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.

@gfriloux
Copy link

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.

@gfriloux
Copy link

gfriloux commented Dec 15, 2017

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.

@killkrt
Copy link

killkrt commented Jul 27, 2019

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).

@jebrosen
Copy link
Collaborator

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 #33 (comment) 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.

@ijackson
Copy link
Contributor

ijackson commented Jul 5, 2020

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.

@SergioBenitez SergioBenitez modified the milestones: 0.8.0, 0.5.0 Aug 6, 2020
@SergioBenitez
Copy link
Member

Pushing this up to 0.5.

@SergioBenitez
Copy link
Member

I'm, sadly, going to push this to 0.6 to clear the way for 0.5. It's likely we can have a solution as a point release in the 0.5 series, however.

@0o-de-lally
Copy link

newb here. I see this is a long running thread, so unsure if this is the right place for these comments. I've run the sse demo in /examples. Looks powerful! Happy to contribute to get this to production.

As of Mar/2021 how do the proposal above change the behavior or API in the demo?
BTW, (and you may be aware of this) when I try the same example code, except using published rocket and rocket-contrib as the dependencies leads to a this error:

error[E0277]: the trait bound `Vec<Route>: std::convert::From<StaticFiles>` is not satisfied
  --> src/main.rs:58:10
   |
58 |         .mount("/", StaticFiles::from("static"))
   |          ^^^^^ the trait `std::convert::From<StaticFiles>` is not implemented for `Vec<Route>`

@SergioBenitez
Copy link
Member

@lpgeiger You're mixing documentation/examples, and perhaps code, from master and 0.4. Neither master nor v0.4 include built-in support for SSE. v0.4 recently gained the ability to implement SSE externally, and we added an sse example to the repository doing just this. master has had support for external implementation of SSE since it became async, but we do not have an example on how to implement support externally; @jebrosen has as example, however.

In general, "nice" SSE support is blocked on a write-based API for responses. We can emulate such an API in Rocket with a performance cost, but it sub-optimal. Ideally, an underlying HTTP library would support such an API, but that is not the case today.

BTW, (and you may be aware of this) when I try the same example code, except using published rocket and rocket-contrib as the dependencies leads to a this error:

This example works in both v0.4 and master, and is tested by the CI on every commit. I suspect you're mixing versions in your Cargo.toml, however. Ensure that your rocket and rocket_contrib versions point at the same version.

@DanielJoyce
Copy link

Reading up on this very thing, http2 can basically do websockets with fetch api and sse.

@SergioBenitez
Copy link
Member

SergioBenitez commented May 23, 2021

Bringing this back to 0.5, thanks to @jebrosen's nearly complete implementation!

@SergioBenitez SergioBenitez modified the milestones: 0.6.0, 0.5.0 May 23, 2021
@SergioBenitez
Copy link
Member

SergioBenitez commented Jun 1, 2021

Support has landed! See the EventStream and Event docs and the new chat example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted An accepted request or suggestion request Request for new functionality
Projects
None yet
Development

No branches or pull requests