1use crate::Conn;
19use futures_lite::{AsyncRead, stream::Stream};
20use std::{
21 collections::VecDeque,
22 error::Error,
23 fmt::{self, Debug, Display, Formatter},
24 ops::{Deref, DerefMut},
25 pin::Pin,
26 task::{Context, Poll, ready},
27 time::Duration,
28};
29use trillium_http::{KnownHeaderName, Status};
30
31const READ_BUF_LEN: usize = 8 * 1024;
32
33impl Conn {
34 pub async fn into_sse(mut self) -> Result<EventStream, SseError> {
52 if self.status().is_some() {
53 return Err(SseError::new(self, SseErrorKind::AlreadyExecuted));
54 }
55
56 let accept = self.request_headers().get_str(KnownHeaderName::Accept);
60 if accept.is_none_or(|accept| accept.trim() == "*/*") {
61 self.request_headers_mut()
62 .insert(KnownHeaderName::Accept, "text/event-stream");
63 }
64
65 if let Err(e) = (&mut self).await {
66 return Err(SseError::new(self, e.into()));
67 }
68
69 let status = self.status().expect("Response did not include status");
70 if !status.is_success() {
71 return Err(SseError::new(self, SseErrorKind::Status(status)));
72 }
73
74 let content_type = self
75 .response_headers()
76 .get_str(KnownHeaderName::ContentType);
77 if !content_type.is_some_and(is_event_stream) {
78 let content_type = content_type.map(String::from);
79 return Err(SseError::new(
80 self,
81 SseErrorKind::UnexpectedContentType(content_type),
82 ));
83 }
84
85 EventStream::new(self)
86 }
87}
88
89fn is_event_stream(content_type: &str) -> bool {
92 content_type
93 .split(';')
94 .next()
95 .is_some_and(|media_type| media_type.trim().eq_ignore_ascii_case("text/event-stream"))
96}
97
98#[derive(Debug, Clone, Eq, PartialEq)]
107pub struct Event {
108 data: String,
109 event_type: Option<String>,
110 id: Option<String>,
111 retry: Option<Duration>,
112}
113
114impl Event {
115 #[must_use]
117 pub fn data(&self) -> &str {
118 &self.data
119 }
120
121 #[must_use]
123 pub fn event_type(&self) -> Option<&str> {
124 self.event_type.as_deref()
125 }
126
127 #[must_use]
129 pub fn id(&self) -> Option<&str> {
130 self.id.as_deref()
131 }
132
133 #[must_use]
138 pub fn retry(&self) -> Option<Duration> {
139 self.retry
140 }
141}
142
143#[derive(Debug)]
150pub struct EventStream {
151 conn: Conn,
152 decoder: Decoder,
153 pending: VecDeque<Event>,
154 read_buf: Box<[u8]>,
155 done: bool,
156}
157
158impl EventStream {
159 pub fn new(conn: Conn) -> Result<Self, SseError> {
172 if conn.status().is_none() {
173 return Err(SseError::new(conn, SseErrorKind::NoBody));
174 }
175
176 Ok(Self {
177 conn,
178 decoder: Decoder::default(),
179 pending: VecDeque::new(),
180 read_buf: vec![0; READ_BUF_LEN].into_boxed_slice(),
181 done: false,
182 })
183 }
184
185 pub fn conn(&self) -> &Conn {
188 &self.conn
189 }
190}
191
192impl Stream for EventStream {
193 type Item = trillium_http::Result<Event>;
194
195 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
196 let this = self.get_mut();
197 loop {
198 if let Some(event) = this.pending.pop_front() {
199 return Poll::Ready(Some(Ok(event)));
200 }
201 if this.done {
202 return Poll::Ready(None);
203 }
204
205 let mut response_body = this.conn.response_body();
206 match ready!(Pin::new(&mut response_body).poll_read(cx, &mut this.read_buf)) {
207 Ok(0) => {
209 this.done = true;
210 return Poll::Ready(None);
211 }
212 Ok(n) => this.decoder.push(&this.read_buf[..n], &mut this.pending),
213 Err(e) => {
214 this.done = true;
215 return Poll::Ready(Some(Err(e.into())));
216 }
217 }
218 }
219 }
220}
221
222#[derive(Debug, Default)]
228struct Decoder {
229 line: Vec<u8>,
230 last_char_was_cr: bool,
231 data: String,
232 event_type: Option<String>,
233 id: Option<String>,
234 retry: Option<Duration>,
235 has_data: bool,
236}
237
238impl Decoder {
239 fn push(&mut self, bytes: &[u8], out: &mut VecDeque<Event>) {
240 for &byte in bytes {
241 match byte {
242 b'\r' => {
243 self.line_done(out);
244 self.last_char_was_cr = true;
245 }
246 b'\n' if self.last_char_was_cr => self.last_char_was_cr = false,
247 b'\n' => self.line_done(out),
248 _ => {
249 self.last_char_was_cr = false;
250 self.line.push(byte);
251 }
252 }
253 }
254 }
255
256 fn line_done(&mut self, out: &mut VecDeque<Event>) {
257 if self.line.is_empty() {
258 self.dispatch(out);
259 } else {
260 let mut line = std::mem::take(&mut self.line);
261 self.process_field(&line);
262 line.clear();
263 self.line = line;
264 }
265 }
266
267 fn process_field(&mut self, line: &[u8]) {
268 let (field, value) = match memchr::memchr(b':', line) {
269 Some(0) => return, Some(colon) => {
271 let value = &line[colon + 1..];
272 let value = value.strip_prefix(b" ").unwrap_or(value);
273 (&line[..colon], value)
274 }
275 None => (line, &b""[..]),
276 };
277
278 match field {
279 b"event" => self.event_type = Some(String::from_utf8_lossy(value).into_owned()),
280
281 b"data" => {
282 self.data.push_str(&String::from_utf8_lossy(value));
283 self.data.push('\n');
284 self.has_data = true;
285 }
286
287 b"id" => {
288 if !value.contains(&0) {
289 self.id = Some(String::from_utf8_lossy(value).into_owned());
290 }
291 }
292
293 b"retry" => {
294 if !value.is_empty()
295 && value.iter().all(u8::is_ascii_digit)
296 && let Ok(ms) = std::str::from_utf8(value).unwrap_or_default().parse()
297 {
298 self.retry = Some(Duration::from_millis(ms));
299 }
300 }
301
302 _ => {}
303 }
304 }
305
306 fn dispatch(&mut self, out: &mut VecDeque<Event>) {
307 if !self.has_data {
308 self.data.clear();
311 self.event_type = None;
312 return;
313 }
314
315 if self.data.ends_with('\n') {
316 self.data.pop();
317 }
318
319 out.push_back(Event {
320 data: std::mem::take(&mut self.data),
321 event_type: self.event_type.take().filter(|s| !s.is_empty()),
322 id: self.id.clone(),
323 retry: self.retry.take(),
324 });
325 self.has_data = false;
326 }
327}
328
329#[derive(thiserror::Error, Debug)]
331#[non_exhaustive]
332pub enum SseErrorKind {
333 #[error(transparent)]
335 Http(#[from] trillium_http::Error),
336
337 #[error("Unexpected response status {0} for SSE request")]
339 Status(Status),
340
341 #[error("Unexpected content-type for SSE request: {0:?}")]
343 UnexpectedContentType(Option<String>),
344
345 #[error(
349 "Conn::into_sse called after execution — build the conn and await into_sse instead of \
350 awaiting the conn separately"
351 )]
352 AlreadyExecuted,
353
354 #[error("SSE conn has no response body to read")]
357 NoBody,
358}
359
360#[derive(Debug)]
365pub struct SseError {
366 pub kind: SseErrorKind,
368 conn: Box<Conn>,
369}
370
371impl SseError {
372 fn new(conn: Conn, kind: SseErrorKind) -> Self {
373 Self {
374 kind,
375 conn: Box::new(conn),
376 }
377 }
378}
379
380impl From<SseError> for Conn {
381 fn from(value: SseError) -> Self {
382 *value.conn
383 }
384}
385
386impl Deref for SseError {
387 type Target = Conn;
388
389 fn deref(&self) -> &Self::Target {
390 &self.conn
391 }
392}
393
394impl DerefMut for SseError {
395 fn deref_mut(&mut self) -> &mut Self::Target {
396 &mut self.conn
397 }
398}
399
400impl Error for SseError {}
401
402impl Display for SseError {
403 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
404 Display::fmt(&self.kind, f)
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411
412 fn decode(input: &[u8]) -> Vec<Event> {
416 let mut whole = Decoder::default();
417 let mut whole_out = VecDeque::new();
418 whole.push(input, &mut whole_out);
419
420 let mut split = Decoder::default();
421 let mut split_out = VecDeque::new();
422 for byte in input {
423 split.push(&[*byte], &mut split_out);
424 }
425
426 assert_eq!(whole_out, split_out, "chunked decode diverged from whole");
427 whole_out.into()
428 }
429
430 #[test]
431 fn fields_comments_and_terminators() {
432 let events =
433 decode(b": this is a comment\nevent: greeting\ndata: hello\nid: 42\nretry: 3000\n\n");
434 assert_eq!(events.len(), 1);
435 let event = &events[0];
436 assert_eq!(event.data(), "hello");
437 assert_eq!(event.event_type(), Some("greeting"));
438 assert_eq!(event.id(), Some("42"));
439 assert_eq!(event.retry(), Some(Duration::from_millis(3000)));
440 }
441
442 #[test]
443 fn multiline_data_joins_with_newline() {
444 let events = decode(b"data: one\ndata: two\ndata:three\n\n");
445 assert_eq!(events[0].data(), "one\ntwo\nthree");
447 }
448
449 #[test]
450 fn crlf_and_cr_terminators() {
451 let crlf = decode(b"data: a\r\n\r\n");
452 assert_eq!(crlf[0].data(), "a");
453 let cr = decode(b"data: b\r\r");
454 assert_eq!(cr[0].data(), "b");
455 }
456
457 #[test]
458 fn empty_data_line_dispatches_empty_event() {
459 let events = decode(b"data\n\n");
461 assert_eq!(events.len(), 1);
462 assert_eq!(events[0].data(), "");
463 }
464
465 #[test]
466 fn blank_lines_without_data_dispatch_nothing() {
467 assert!(decode(b"\n\n\n").is_empty());
468 assert!(decode(b": just a comment\n\n").is_empty());
469 }
470
471 #[test]
472 fn incomplete_trailing_event_is_discarded() {
473 assert!(decode(b"data: pending\n").is_empty());
475 }
476
477 #[test]
478 fn id_persists_across_events_retry_does_not() {
479 let events = decode(b"id: 1\nretry: 500\ndata: a\n\ndata: b\n\n");
480 assert_eq!(events[0].id(), Some("1"));
481 assert_eq!(events[0].retry(), Some(Duration::from_millis(500)));
482 assert_eq!(events[1].id(), Some("1"));
484 assert_eq!(events[1].retry(), None);
485 }
486
487 #[test]
488 fn invalid_retry_is_ignored() {
489 let events = decode(b"retry: not-a-number\ndata: a\n\n");
490 assert_eq!(events[0].retry(), None);
491 }
492}