1#![doc(
39 alias = "server sent",
40 alias = "server-sent",
41 alias = "server sent events",
42 alias = "server-sent events",
43 alias = "event-stream"
44)]
45
46use std::{
47 pin::Pin,
48 task::{Context, Poll},
49 time::Duration,
50};
51
52use actix_web::{
53 HttpRequest, HttpResponse, Responder,
54 body::{BodySize, BoxBody, MessageBody},
55 http::header::ContentEncoding,
56};
57use bytes::Bytes;
58use bytestring::ByteString;
59use futures_core::Stream;
60use pin_project_lite::pin_project;
61use serde::Serialize;
62use tokio::{
63 sync::mpsc,
64 time::{Interval, interval},
65};
66use tokio_stream::wrappers::ReceiverStream;
67
68use crate::{
69 BoxError,
70 header::{CacheControl, CacheDirective},
71 util::InfallibleStream,
72};
73
74#[must_use]
105#[derive(Debug, Clone)]
106pub struct Data {
107 id: Option<ByteString>,
108 event: Option<ByteString>,
109 data: ByteString,
110}
111
112impl Data {
113 pub fn new(data: impl Into<ByteString>) -> Self {
121 Self {
122 id: None,
123 event: None,
124 data: data.into(),
125 }
126 }
127
128 pub fn new_json(data: impl Serialize) -> Result<Self, serde_json::Error> {
142 Ok(Self {
143 id: None,
144 event: None,
145 data: serde_json::to_string(&data)?.into(),
146 })
147 }
148
149 pub fn set_data(&mut self, data: impl Into<ByteString>) {
151 self.data = data.into();
152 }
153
154 pub fn id(mut self, id: impl Into<ByteString>) -> Self {
156 self.id = Some(id.into());
157 self
158 }
159
160 pub fn set_id(&mut self, id: impl Into<ByteString>) {
162 self.id = Some(id.into());
163 }
164
165 pub fn event(mut self, event: impl Into<ByteString>) -> Self {
167 self.event = Some(event.into());
168 self
169 }
170
171 pub fn set_event(&mut self, event: impl Into<ByteString>) {
173 self.event = Some(event.into());
174 }
175}
176
177impl From<Data> for Event {
178 fn from(data: Data) -> Self {
179 Self::Data(data)
180 }
181}
182
183#[must_use]
185#[derive(Debug, Clone)]
186pub enum Event {
187 Data(Data),
200
201 Comment(ByteString),
210}
211
212impl Event {
213 fn into_bytes(self) -> Result<Bytes, BoxError> {
215 let event = match self {
216 Self::Data(Data { id, event, data }) => russe::Event::Message(russe::Message {
217 data,
218 event,
219 id,
220 retry: None,
221 }),
222
223 Self::Comment(comment) => russe::Event::Comment(comment),
224 };
225
226 event
227 .into_bytestring()
228 .map(ByteString::into_bytes)
229 .map_err(Into::into)
230 }
231}
232
233pin_project! {
234 #[must_use]
239 #[derive(Debug)]
240 pub struct Sse<S> {
241 #[pin]
242 stream: S,
243 keep_alive: Option<Interval>,
244 retry_interval: Option<Duration>,
245 }
246}
247
248impl<S, E> Sse<S>
249where
250 S: Stream<Item = Result<Event, E>> + 'static,
251 E: Into<BoxError>,
252{
253 pub fn from_stream(stream: S) -> Self {
255 Self {
256 stream,
257 keep_alive: None,
258 retry_interval: None,
259 }
260 }
261}
262
263impl<S> Sse<InfallibleStream<S>>
264where
265 S: Stream<Item = Event> + 'static,
266{
267 pub fn from_infallible_stream(stream: S) -> Self {
269 Sse::from_stream(InfallibleStream::new(stream))
270 }
271}
272
273impl<E> Sse<ReceiverStream<Result<Event, E>>>
274where
275 E: Into<BoxError> + 'static,
276{
277 pub fn from_receiver(receiver: mpsc::Receiver<Result<Event, E>>) -> Self {
279 Self::from_stream(ReceiverStream::new(receiver))
280 }
281}
282
283impl Sse<InfallibleStream<ReceiverStream<Event>>> {
284 pub fn from_infallible_receiver(receiver: mpsc::Receiver<Event>) -> Self {
286 Self::from_stream(InfallibleStream::new(ReceiverStream::new(receiver)))
287 }
288}
289
290impl<S> Sse<S> {
291 pub fn with_keep_alive(mut self, keep_alive_period: Duration) -> Self {
295 let mut int = interval(keep_alive_period);
296 int.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
297
298 self.keep_alive = Some(int);
299 self
300 }
301
302 pub fn with_retry_duration(mut self, retry: Duration) -> Self {
306 self.retry_interval = Some(retry);
307 self
308 }
309}
310
311impl<S, E> Responder for Sse<S>
312where
313 S: Stream<Item = Result<Event, E>> + 'static,
314 E: Into<BoxError>,
315{
316 type Body = BoxBody;
317
318 fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
319 HttpResponse::Ok()
320 .content_type(russe::MEDIA_TYPE_STR)
321 .insert_header(ContentEncoding::Identity)
322 .insert_header(CacheControl(vec![CacheDirective::NoCache]))
323 .body(self)
324 }
325}
326
327impl<S, E> MessageBody for Sse<S>
328where
329 S: Stream<Item = Result<Event, E>>,
330 E: Into<BoxError>,
331{
332 type Error = BoxError;
333
334 fn size(&self) -> BodySize {
335 BodySize::Stream
336 }
337
338 fn poll_next(
339 self: Pin<&mut Self>,
340 cx: &mut Context<'_>,
341 ) -> Poll<Option<Result<Bytes, Self::Error>>> {
342 let this = self.project();
343
344 if let Some(retry) = this.retry_interval.take() {
345 cx.waker().wake_by_ref();
346 return Poll::Ready(Some(
347 russe::Event::Retry(retry)
348 .into_bytestring()
349 .map(ByteString::into_bytes)
350 .map_err(Into::into),
351 ));
352 }
353
354 if let Poll::Ready(msg) = this.stream.poll_next(cx) {
355 return match msg {
356 Some(Ok(msg)) => Poll::Ready(Some(msg.into_bytes())),
357 Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
358 None => Poll::Ready(None),
359 };
360 }
361
362 if let Some(keep_alive) = this.keep_alive
363 && keep_alive.poll_tick(cx).is_ready()
364 {
365 return Poll::Ready(Some(
366 russe::Event::Comment("keep-alive".into())
367 .into_bytestring()
368 .map(ByteString::into_bytes)
369 .map_err(Into::into),
370 ));
371 }
372
373 Poll::Pending
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use std::convert::Infallible;
380
381 use actix_web::{body, test::TestRequest};
382 use futures_util::{FutureExt as _, StreamExt as _, future::poll_fn, stream, task::noop_waker};
383 use tokio::time::sleep;
384
385 use super::*;
386 use crate::assert_response_matches;
387
388 #[actix_web::test]
389 async fn existing_data_api_is_preserved() {
390 let mut data = Data::new("old data").id("old ID").event("old event");
391
392 data.set_data("payload");
393 data.set_id("42");
394 data.set_event("update");
395
396 let json = Data::new_json(serde_json::json!({ "bar": 42 })).unwrap();
397 let events = stream::iter([Event::Data(data), json.into()]);
398 let sse = Sse::from_infallible_stream(events);
399
400 assert_eq!(
401 body::to_bytes(sse).await.unwrap(),
402 "id: 42\nevent: update\ndata: payload\n\ndata: {\"bar\":42}\n\n",
403 );
404 }
405
406 #[actix_web::test]
407 async fn message_fields_are_encoded() {
408 let events = stream::iter([
409 Event::Comment("foo".into()),
410 Data::new("\n").into(),
411 Data::new("foo").id("42").event("bar").into(),
412 ]);
413 let sse = Sse::from_infallible_stream(events);
414
415 assert_eq!(
416 body::to_bytes(sse).await.unwrap(),
417 ": foo\n\ndata: \ndata: \n\nid: 42\nevent: bar\ndata: foo\n\n",
418 );
419 }
420
421 #[actix_web::test]
422 async fn sse_from_external_streams() {
423 let st = stream::empty::<Result<_, Infallible>>();
424 let sse = Sse::from_stream(st);
425 assert_eq!(body::to_bytes(sse).await.unwrap(), "");
426
427 let st = stream::once(async { Ok::<_, Infallible>(Event::Data(Data::new("foo"))) });
428 let sse = Sse::from_stream(st);
429 assert_eq!(body::to_bytes(sse).await.unwrap(), "data: foo\n\n");
430
431 let st = stream::repeat(Ok::<_, Infallible>(Event::Data(Data::new("foo")))).take(2);
432 let sse = Sse::from_stream(st);
433 assert_eq!(
434 body::to_bytes(sse).await.unwrap(),
435 "data: foo\n\ndata: foo\n\n",
436 );
437 }
438
439 #[actix_web::test]
440 async fn normalizes_line_endings() {
441 let events = stream::iter([
442 Event::Data(Data::new("first\r\nsecond\rthird\n")),
443 Event::Comment("first\r\nsecond\rthird\n".into()),
444 ]);
445 let sse = Sse::from_infallible_stream(events);
446
447 assert_eq!(
448 body::to_bytes(sse).await.unwrap(),
449 "data: first\ndata: second\ndata: third\ndata: \n\n: first\n: second\n: third\n: \n\n",
450 );
451 }
452
453 #[actix_web::test]
454 async fn rejects_invalid_message_fields() {
455 for (id, event) in [
456 ("bad\nid", "update"),
457 ("bad\rid", "update"),
458 ("bad\0id", "update"),
459 ("42", "bad\nevent"),
460 ("42", "bad\revent"),
461 ] {
462 let events = stream::iter([Event::Data(Data::new("payload").id(id).event(event))]);
463 let sse = Sse::from_infallible_stream(events);
464
465 let err = body::to_bytes(sse).await.unwrap_err();
466
467 assert!(matches!(
468 err.downcast_ref::<russe::Error>(),
469 Some(russe::Error::Invalid),
470 ));
471 }
472 }
473
474 #[test]
475 fn retry_is_first_msg() {
476 let waker = noop_waker();
477 let mut cx = Context::from_waker(&waker);
478
479 let mut sse = Sse::from_stream(InfallibleStream::new(tokio_stream::empty()))
480 .with_retry_duration(Duration::from_millis(42));
481 match Pin::new(&mut sse).poll_next(&mut cx) {
482 Poll::Ready(Some(Ok(bytes))) => assert_eq!(bytes, "retry: 42\n\n"),
483 res => panic!("poll should return retry message, got {res:?}"),
484 }
485 }
486
487 #[actix_web::test]
488 async fn retry_precedes_stream_events() {
489 let events = stream::iter([Event::Comment("first".into())]);
490 let sse =
491 Sse::from_infallible_stream(events).with_retry_duration(Duration::from_millis(42));
492
493 assert_eq!(
494 body::to_bytes(sse).await.unwrap(),
495 "retry: 42\n\n: first\n\n",
496 );
497 }
498
499 #[actix_web::test]
500 async fn receiver_errors_are_preserved() {
501 let (sender, receiver) = mpsc::channel(1);
502 let sse = Sse::from_receiver(receiver);
503
504 sender
505 .send(Err(std::io::Error::other("stream failed")))
506 .await
507 .unwrap();
508
509 let err = body::to_bytes(sse).await.unwrap_err();
510
511 assert_eq!(
512 err.downcast_ref::<std::io::Error>().unwrap().to_string(),
513 "stream failed",
514 );
515 }
516
517 #[actix_web::test]
518 async fn closed_receiver_ends_with_keep_alive() {
519 let (sender, receiver) = mpsc::channel(1);
520 let sse = Sse::from_infallible_receiver(receiver).with_keep_alive(Duration::from_millis(4));
521
522 drop(sender);
523
524 assert_eq!(body::to_bytes(sse).await.unwrap(), "");
525 }
526
527 #[actix_web::test]
528 async fn appropriate_headers_are_set_on_responder() {
529 let st = stream::empty::<Result<_, Infallible>>();
530 let sse = Sse::from_stream(st);
531
532 let res = sse.respond_to(&TestRequest::default().to_http_request());
533
534 assert_response_matches!(res, OK;
535 "content-type" => "text/event-stream"
536 "content-encoding" => "identity"
537 "cache-control" => "no-cache"
538 );
539 }
540
541 #[actix_web::test]
542 async fn messages_are_received_from_sender() {
543 let (sender, receiver) = tokio::sync::mpsc::channel(2);
544 let mut sse = Sse::from_infallible_receiver(receiver);
545
546 assert!(
547 poll_fn(|cx| Pin::new(&mut sse).poll_next(cx))
548 .now_or_never()
549 .is_none()
550 );
551
552 sender
553 .send(Event::Data(Data::new("bar").event("foo")))
554 .await
555 .unwrap();
556
557 match poll_fn(|cx| Pin::new(&mut sse).poll_next(cx)).now_or_never() {
558 Some(Some(Ok(bytes))) => assert_eq!(bytes, "event: foo\ndata: bar\n\n"),
559 res => panic!("poll should return data message, got {res:?}"),
560 }
561 }
562
563 #[actix_web::test]
564 async fn keep_alive_is_sent() {
565 let waker = noop_waker();
566 let mut cx = Context::from_waker(&waker);
567
568 let (sender, receiver) = tokio::sync::mpsc::channel(2);
569 let mut sse =
570 Sse::from_infallible_receiver(receiver).with_keep_alive(Duration::from_millis(4));
571
572 assert!(Pin::new(&mut sse).poll_next(&mut cx).is_pending());
573
574 sleep(Duration::from_millis(20)).await;
575
576 match Pin::new(&mut sse).poll_next(&mut cx) {
577 Poll::Ready(Some(Ok(bytes))) => assert_eq!(bytes, ": keep-alive\n\n"),
578 res => panic!("poll should return keep-alive message, got {res:?}"),
579 }
580
581 assert!(Pin::new(&mut sse).poll_next(&mut cx).is_pending());
582
583 sender.send(Event::Data(Data::new("foo"))).await.unwrap();
584
585 match Pin::new(&mut sse).poll_next(&mut cx) {
586 Poll::Ready(Some(Ok(bytes))) => assert_eq!(bytes, "data: foo\n\n"),
587 res => panic!("poll should return data message, got {res:?}"),
588 }
589 }
590}