netget/server/sse/
wire.rs1use anyhow::{ensure, Result};
4
5pub const MAX_LINE_BYTES: usize = 64 * 1024;
7pub const MAX_EVENT_BYTES: usize = 1024 * 1024;
9
10fn single_line(field: &str, value: &str) -> Result<()> {
11 ensure!(
12 !value.contains(['\r', '\n']),
13 "{field} is one line: it may not contain CR or LF"
14 );
15 Ok(())
16}
17
18pub fn encode_event(
20 event: Option<&str>,
21 id: Option<&str>,
22 data: &str,
23 retry_ms: Option<u64>,
24) -> Result<Vec<u8>> {
25 ensure!(
26 data.len() <= MAX_EVENT_BYTES,
27 "data is at most {MAX_EVENT_BYTES} bytes"
28 );
29 let mut out = String::with_capacity(data.len() + 64);
30 if let Some(event) = event.filter(|e| !e.is_empty()) {
31 single_line("event", event)?;
32 out.push_str("event: ");
33 out.push_str(event);
34 out.push('\n');
35 }
36 if let Some(id) = id {
37 single_line("id", id)?;
38 ensure!(
39 !id.contains('\0'),
40 "id may not contain NUL (the client would ignore it)"
41 );
42 out.push_str("id: ");
43 out.push_str(id);
44 out.push('\n');
45 }
46 if let Some(retry) = retry_ms {
47 out.push_str(&format!("retry: {retry}\n"));
48 }
49 for line in data.split("\r\n").flat_map(|l| l.split(['\r', '\n'])) {
50 out.push_str("data: ");
51 out.push_str(line);
52 out.push('\n');
53 }
54 out.push('\n');
55 Ok(out.into_bytes())
56}
57
58pub fn encode_comment(text: &str) -> Result<Vec<u8>> {
60 single_line("comment", text)?;
61 Ok(format!(": {text}\n\n").into_bytes())
62}
63
64pub fn encode_retry(retry_ms: u64) -> Vec<u8> {
66 format!("retry: {retry_ms}\n\n").into_bytes()
67}
68
69#[derive(Debug, Clone, PartialEq)]
71pub struct Event {
72 pub event: String,
74 pub data: String,
75 pub last_event_id: String,
77}
78
79#[derive(Default)]
81pub struct Parser {
82 pending: Vec<u8>,
83 started: bool,
84 skip_lf: bool,
85 event: String,
86 data: String,
87 has_data: bool,
88 pub last_event_id: String,
89 pub retry_ms: Option<u64>,
91}
92
93impl Parser {
94 pub fn with_last_event_id(id: &str) -> Self {
95 Self {
96 last_event_id: id.to_string(),
97 ..Self::default()
98 }
99 }
100
101 pub fn reset_stream(&mut self) {
103 self.pending.clear();
104 self.started = false;
105 self.skip_lf = false;
106 self.event.clear();
107 self.data.clear();
108 self.has_data = false;
109 }
110
111 pub fn feed(&mut self, bytes: &[u8]) -> Result<Vec<Event>> {
113 let mut out = Vec::new();
114 for &b in bytes {
115 if self.skip_lf {
116 self.skip_lf = false;
117 if b == b'\n' {
118 continue;
119 }
120 }
121 match b {
122 b'\r' | b'\n' => {
123 self.skip_lf = b == b'\r';
124 let line = std::mem::take(&mut self.pending);
125 if let Some(e) = self.line(&line)? {
126 out.push(e);
127 }
128 }
129 _ => {
130 ensure!(
131 self.pending.len() < MAX_LINE_BYTES,
132 "an event-stream line longer than {MAX_LINE_BYTES} bytes"
133 );
134 self.pending.push(b);
135 }
136 }
137 }
138 Ok(out)
139 }
140
141 fn line(&mut self, raw: &[u8]) -> Result<Option<Event>> {
142 let mut line = String::from_utf8_lossy(raw).into_owned();
143 if !self.started {
144 self.started = true;
145 if let Some(rest) = line.strip_prefix('\u{feff}') {
146 line = rest.to_string();
147 }
148 }
149 if line.is_empty() {
150 return Ok(self.dispatch());
151 }
152 if line.starts_with(':') {
153 return Ok(None);
154 }
155 let (field, value) = match line.split_once(':') {
156 Some((f, v)) => (f.to_string(), v.strip_prefix(' ').unwrap_or(v).to_string()),
157 None => (line.clone(), String::new()),
158 };
159 match field.as_str() {
160 "event" => self.event = value,
161 "data" => {
162 ensure!(
163 self.data.len() + value.len() < MAX_EVENT_BYTES,
164 "an event's data over {MAX_EVENT_BYTES} bytes"
165 );
166 self.data.push_str(&value);
167 self.data.push('\n');
168 self.has_data = true;
169 }
170 "id" if !value.contains('\0') => self.last_event_id = value,
171 "retry" if !value.is_empty() && value.bytes().all(|b| b.is_ascii_digit()) => {
172 self.retry_ms = value.parse().ok();
173 }
174 _ => {}
175 }
176 Ok(None)
177 }
178
179 fn dispatch(&mut self) -> Option<Event> {
180 let event = std::mem::take(&mut self.event);
181 if !self.has_data {
182 return None;
183 }
184 self.has_data = false;
185 let mut data = std::mem::take(&mut self.data);
186 data.pop();
187 Some(Event {
188 event: if event.is_empty() {
189 "message".into()
190 } else {
191 event
192 },
193 data,
194 last_event_id: self.last_event_id.clone(),
195 })
196 }
197}