1use std::collections::VecDeque;
5use std::io::Read;
6
7use crate::error::HttpError;
8
9#[derive(Debug, Clone, Default, PartialEq, Eq)]
10pub struct SseEvent {
11 pub event: String,
13 pub data: String,
14 pub id: Option<String>,
15 pub retry_ms: Option<u64>,
16}
17
18#[derive(Debug)]
20pub struct SseDecoder {
21 line: Vec<u8>,
22 prev_cr: bool,
23 first: bool,
24 event: String,
25 data: Vec<String>,
26 has_data: bool,
27 id: Option<String>,
28 retry_ms: Option<u64>,
29 max_line: usize,
30 max_event: usize,
31 event_bytes: usize,
32 eager: bool,
34}
35
36impl Default for SseDecoder {
37 fn default() -> Self {
38 Self::new(1024 * 1024)
39 }
40}
41
42impl SseDecoder {
43 pub fn new(max_line_bytes: usize) -> Self {
45 Self::with_limits(max_line_bytes, usize::MAX)
46 }
47
48 pub fn with_limits(max_line_bytes: usize, max_event_bytes: usize) -> Self {
51 Self {
52 line: Vec::new(),
53 prev_cr: false,
54 first: true,
55 event: String::new(),
56 data: Vec::new(),
57 has_data: false,
58 id: None,
59 retry_ms: None,
60 max_line: max_line_bytes,
61 max_event: max_event_bytes,
62 event_bytes: 0,
63 eager: false,
64 }
65 }
66
67 pub fn eager() -> Self {
72 Self {
73 eager: true,
74 ..Self::default()
75 }
76 }
77
78 pub fn with_eager(mut self) -> Self {
80 self.eager = true;
81 self
82 }
83
84 pub fn has_pending(&self) -> bool {
88 !self.line.is_empty() || self.has_data
89 }
90
91 pub fn finish(&mut self) -> Result<Option<SseEvent>, HttpError> {
96 if !self.line.is_empty() {
97 let line = std::mem::take(&mut self.line);
98 if let Some(ev) = self.handle_line(&line)? {
99 return Ok(Some(ev));
100 }
101 }
102 Ok(self.dispatch())
103 }
104
105 pub fn push(&mut self, bytes: &[u8]) -> Result<Vec<SseEvent>, HttpError> {
106 let mut out = Vec::new();
107 for &b in bytes {
108 if self.prev_cr {
109 self.prev_cr = false;
110 if b == b'\n' {
111 continue; }
113 }
114 match b {
115 b'\n' | b'\r' => {
116 self.prev_cr = b == b'\r';
117 let line = std::mem::take(&mut self.line);
118 if let Some(ev) = self.handle_line(&line)? {
119 out.push(ev);
120 }
121 }
122 _ => {
123 self.line.push(b);
124 if self.line.len() > self.max_line {
125 return Err(HttpError::Stream("SSE line exceeds limit".into()));
126 }
127 }
128 }
129 }
130 Ok(out)
131 }
132
133 fn handle_line(&mut self, raw: &[u8]) -> Result<Option<SseEvent>, HttpError> {
134 let mut line = raw;
135 if self.first {
136 self.first = false;
137 if line.starts_with(&[0xEF, 0xBB, 0xBF]) {
138 line = &line[3..];
139 }
140 }
141 if line.is_empty() {
142 return Ok(self.dispatch());
143 }
144 let text = std::str::from_utf8(line)
145 .map_err(|_| HttpError::Stream("SSE line is not valid UTF-8".into()))?;
146 if text.starts_with(':') {
147 return Ok(None);
148 }
149 let (field, value) = match text.split_once(':') {
150 Some((f, v)) => (f, v.strip_prefix(' ').unwrap_or(v)),
151 None => (text, ""),
152 };
153 if matches!(field, "event" | "data") {
154 self.event_bytes = self.event_bytes.saturating_add(value.len() + 1);
155 if self.event_bytes > self.max_event {
156 return Err(HttpError::Stream("SSE event exceeds limit".into()));
157 }
158 }
159 match field {
160 "event" => self.event = value.to_string(),
161 "data" if self.eager => {
162 self.event_bytes = 0;
163 let event = if self.event.is_empty() {
164 "message".to_string()
165 } else {
166 self.event.clone()
167 };
168 return Ok(Some(SseEvent {
169 event,
170 data: value.to_string(),
171 id: self.id.clone(),
172 retry_ms: self.retry_ms.take(),
173 }));
174 }
175 "data" => {
176 self.data.push(value.to_string());
177 self.has_data = true;
178 }
179 "id" if !value.contains('\0') => self.id = Some(value.to_string()),
180 "retry" => {
181 if let Ok(ms) = value.parse::<u64>() {
182 self.retry_ms = Some(ms);
183 }
184 }
185 _ => {}
186 }
187 Ok(None)
188 }
189
190 fn dispatch(&mut self) -> Option<SseEvent> {
191 let had = self.has_data;
192 self.event_bytes = 0;
193 let event = std::mem::take(&mut self.event);
194 let data = std::mem::take(&mut self.data);
195 self.has_data = false;
196 let retry_ms = self.retry_ms.take();
197 if !had {
198 return None;
199 }
200 Some(SseEvent {
201 event: if event.is_empty() {
202 "message".into()
203 } else {
204 event
205 },
206 data: data.join("\n"),
207 id: self.id.clone(),
208 retry_ms,
209 })
210 }
211}
212
213#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
215pub enum EofPolicy {
216 #[default]
218 Drop,
219 Deliver,
221 Fail,
224}
225
226pub struct SseReader<R: Read> {
229 reader: R,
230 decoder: SseDecoder,
231 queue: VecDeque<SseEvent>,
232 done: bool,
233 eof: EofPolicy,
234}
235
236impl<R: Read> SseReader<R> {
237 pub fn new(reader: R) -> Self {
238 Self::with_decoder(reader, SseDecoder::default())
239 }
240
241 pub fn with_decoder(reader: R, decoder: SseDecoder) -> Self {
243 Self {
244 reader,
245 decoder,
246 queue: VecDeque::new(),
247 done: false,
248 eof: EofPolicy::Drop,
249 }
250 }
251
252 pub fn eof_policy(mut self, policy: EofPolicy) -> Self {
254 self.eof = policy;
255 self
256 }
257}
258
259impl<R: Read> Iterator for SseReader<R> {
260 type Item = Result<SseEvent, HttpError>;
261 fn next(&mut self) -> Option<Self::Item> {
262 let mut buf = [0u8; 8192];
263 loop {
264 if let Some(ev) = self.queue.pop_front() {
265 return Some(Ok(ev));
266 }
267 if self.done {
268 return None;
269 }
270 match self.reader.read(&mut buf) {
271 Ok(0) => {
272 self.done = true;
273 match self.eof {
274 EofPolicy::Drop => {}
275 EofPolicy::Deliver => match self.decoder.finish() {
276 Ok(Some(ev)) => self.queue.push_back(ev),
277 Ok(None) => {}
278 Err(e) => return Some(Err(e)),
279 },
280 EofPolicy::Fail => {
281 if self.decoder.has_pending() {
282 return Some(Err(HttpError::Stream(
283 "SSE stream ended inside an event".into(),
284 )));
285 }
286 }
287 }
288 }
289 Ok(n) => match self.decoder.push(&buf[..n]) {
290 Ok(events) => self.queue.extend(events),
291 Err(e) => {
292 self.done = true;
293 return Some(Err(e));
294 }
295 },
296 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
297 Err(e) => {
298 self.done = true;
299 return Some(Err(match e.kind() {
300 std::io::ErrorKind::TimedOut => {
301 HttpError::timeout(e.to_string(), crate::error::Phase::Body)
302 }
303 _ => HttpError::transport(e.to_string(), crate::error::Phase::Body),
304 }));
305 }
306 }
307 }
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn eager_delivers_each_data_line_and_finish_flushes_the_tail() {
317 let mut d = SseDecoder::eager();
318 let evs = d.push(b"event: tick\ndata: one\ndata: two\n").unwrap();
319 assert_eq!(evs.len(), 2);
320 assert_eq!(evs[0].event, "tick");
321 assert_eq!(evs[0].data, "one");
322 assert_eq!(evs[1].data, "two");
323 assert!(!d.has_pending());
324 assert!(d.push(b"data: tail").unwrap().is_empty());
325 assert!(d.has_pending());
326 let last = d.finish().unwrap().unwrap();
327 assert_eq!(last.data, "tail");
328 assert!(!d.has_pending());
329 }
330
331 #[test]
332 fn default_mode_finish_delivers_a_frame_missing_its_blank_line() {
333 let mut d = SseDecoder::new(1024);
334 assert!(d.push(b"data: a\n").unwrap().is_empty());
335 assert!(d.has_pending());
336 let ev = d.finish().unwrap().unwrap();
337 assert_eq!(ev.data, "a");
338 assert_eq!(ev.event, "message");
339 assert!(!d.has_pending());
340 }
341
342 #[test]
343 fn reader_eof_policies_drop_deliver_or_fail() {
344 let input: &[u8] = b"data: a\n\ndata: b";
345 let dropped: Vec<String> = SseReader::new(input).map(|r| r.unwrap().data).collect();
346 assert_eq!(dropped, vec!["a"]);
347 let delivered: Vec<String> = SseReader::new(input)
348 .eof_policy(EofPolicy::Deliver)
349 .map(|r| r.unwrap().data)
350 .collect();
351 assert_eq!(delivered, vec!["a", "b"]);
352 let failed: Vec<Result<SseEvent, HttpError>> =
353 SseReader::new(input).eof_policy(EofPolicy::Fail).collect();
354 assert!(failed.last().unwrap().is_err());
355 }
356}