rws_server/test_client/
sse.rs1use std::io::Read;
14use std::sync::mpsc::{self, Receiver, RecvTimeoutError};
15use std::time::{Duration, Instant};
16
17pub const MAX_EVENT_BYTES: usize = 8 * 1024 * 1024;
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct SseFrame {
24 pub event: String,
27 pub data: String,
29 pub id: Option<String>,
32 pub retry: Option<u64>,
34}
35
36#[derive(Debug, Default)]
51pub struct SseParser {
52 line: Vec<u8>,
53 after_cr: bool,
55 started: bool,
57 event: Option<String>,
58 data: String,
59 has_data: bool,
60 id: Option<String>,
61 retry: Option<u64>,
62 pending: usize,
64 comments: usize,
65}
66
67impl SseParser {
68 pub fn new() -> Self {
70 SseParser::default()
71 }
72
73 pub fn comments(&self) -> usize {
75 self.comments
76 }
77
78 pub fn feed(&mut self, mut bytes: &[u8]) -> Result<Vec<SseFrame>, String> {
82 let mut events = Vec::new();
83 if !self.started && !bytes.is_empty() {
84 if bytes.starts_with(&[0xEF, 0xBB, 0xBF]) {
87 bytes = &bytes[3..];
88 }
89 self.started = true;
90 }
91 for &byte in bytes {
92 match byte {
93 b'\n' if self.after_cr => self.after_cr = false,
94 b'\n' | b'\r' => {
95 self.after_cr = byte == b'\r';
96 let line = std::mem::take(&mut self.line);
97 if let Some(event) = self.line_done(&line) {
98 events.push(event);
99 }
100 self.line = line;
101 self.line.clear();
102 }
103 _ => {
104 self.after_cr = false;
105 if self.line.len() + self.pending >= MAX_EVENT_BYTES {
106 *self = SseParser::default();
107 return Err(format!("an SSE event over {MAX_EVENT_BYTES} bytes"));
108 }
109 self.line.push(byte);
110 }
111 }
112 }
113 Ok(events)
114 }
115
116 fn line_done(&mut self, line: &[u8]) -> Option<SseFrame> {
118 if line.is_empty() {
119 let event = self.event.take();
120 let id = self.id.take();
121 let retry = self.retry.take();
122 let data = std::mem::take(&mut self.data);
123 let has_data = std::mem::replace(&mut self.has_data, false);
124 self.pending = 0;
125 if !has_data {
126 return None;
127 }
128 return Some(SseFrame { event: event.unwrap_or_else(|| "message".to_string()), data, id, retry });
129 }
130 if line[0] == b':' {
131 self.comments += 1;
132 return None;
133 }
134 let (name, value) = match line.iter().position(|&b| b == b':') {
135 Some(colon) => {
136 let value = &line[colon + 1..];
137 (&line[..colon], value.strip_prefix(b" ").unwrap_or(value))
138 }
139 None => (line, &b""[..]),
140 };
141 self.pending += line.len() + 1;
144 let value = String::from_utf8_lossy(value).into_owned();
145 match name {
146 b"event" => self.event = Some(value),
147 b"data" => {
148 if self.has_data {
149 self.data.push('\n');
150 }
151 self.data.push_str(&value);
152 self.has_data = true;
153 }
154 b"id" if !value.contains('\0') => self.id = Some(value),
155 b"retry" if !value.is_empty() && value.bytes().all(|b| b.is_ascii_digit()) => {
156 self.retry = value.parse().ok();
157 }
158 _ => {}
159 }
160 None
161 }
162}
163
164enum Chunk {
166 Bytes(Vec<u8>),
167 End,
168 Failed(String),
169}
170
171pub struct TestSse {
173 parser: SseParser,
174 ready: std::collections::VecDeque<SseFrame>,
175 chunks: Option<Receiver<Chunk>>,
176 ended: bool,
177 error: Option<String>,
178}
179
180impl TestSse {
181 pub fn from_bytes(body: &[u8]) -> TestSse {
183 let mut sse = TestSse::empty();
184 sse.take_in(body);
185 sse.ended = true;
186 sse
187 }
188
189 pub fn from_reader(mut reader: Box<dyn Read + Send>) -> TestSse {
194 let (tx, rx) = mpsc::channel();
195 let spawned = std::thread::Builder::new().name("rws-test-sse".to_string()).spawn(move || {
196 let mut buf = vec![0u8; 8192];
197 loop {
198 let chunk = match reader.read(&mut buf) {
199 Ok(0) => Chunk::End,
200 Ok(n) => Chunk::Bytes(buf[..n].to_vec()),
201 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
202 Err(e) => Chunk::Failed(e.to_string()),
203 };
204 let last = !matches!(chunk, Chunk::Bytes(_));
205 if tx.send(chunk).is_err() || last {
206 return;
207 }
208 }
209 });
210 let mut sse = TestSse::empty();
211 match spawned {
212 Ok(_) => sse.chunks = Some(rx),
213 Err(e) => {
214 sse.ended = true;
215 sse.error = Some(format!("couldn't start the SSE reader thread: {e}"));
216 }
217 }
218 sse
219 }
220
221 fn empty() -> TestSse {
222 TestSse { parser: SseParser::new(), ready: Default::default(), chunks: None, ended: false, error: None }
223 }
224
225 fn take_in(&mut self, bytes: &[u8]) {
226 match self.parser.feed(bytes) {
227 Ok(events) => self.ready.extend(events),
228 Err(e) => {
229 self.error = Some(e);
230 self.ended = true;
231 self.chunks = None;
232 }
233 }
234 }
235
236 pub fn next_event(&mut self, timeout: Duration) -> Option<SseFrame> {
239 let deadline = Instant::now() + timeout;
240 loop {
241 if let Some(event) = self.ready.pop_front() {
242 return Some(event);
243 }
244 if self.ended {
245 return None;
246 }
247 let rx = self.chunks.as_ref()?;
248 let left = deadline.saturating_duration_since(Instant::now());
249 match rx.recv_timeout(left) {
250 Ok(Chunk::Bytes(bytes)) => self.take_in(&bytes),
251 Ok(Chunk::End) | Err(RecvTimeoutError::Disconnected) => self.finish(None),
252 Ok(Chunk::Failed(e)) => self.finish(Some(e)),
253 Err(RecvTimeoutError::Timeout) => return None,
254 }
255 }
256 }
257
258 fn finish(&mut self, error: Option<String>) {
259 self.ended = true;
260 self.chunks = None;
261 if error.is_some() {
262 self.error = error;
263 }
264 }
265
266 pub fn events(&mut self, n: usize, timeout: Duration) -> Vec<SseFrame> {
268 let deadline = Instant::now() + timeout;
269 let mut events = Vec::new();
270 while events.len() < n {
271 match self.next_event(deadline.saturating_duration_since(Instant::now())) {
272 Some(event) => events.push(event),
273 None => break,
274 }
275 }
276 events
277 }
278
279 pub fn collect_until_end(&mut self, timeout: Duration) -> Vec<SseFrame> {
283 self.events(usize::MAX, timeout)
284 }
285
286 pub fn is_ended(&self) -> bool {
288 self.ended && self.ready.is_empty()
289 }
290
291 pub fn error(&self) -> Option<&str> {
293 self.error.as_deref()
294 }
295
296 pub fn comments(&self) -> usize {
298 self.parser.comments()
299 }
300}