mattrguerra/cortexmainPrivate on GitHub, published here in full

sse.rs

Language Rust5.9 KB189 lines

crates/cortex-core/src/provider/sse.rs

  1. //! A byte-level Server-Sent Events parser.
  2. //!
  3. //! Network chunks can split anywhere: in the middle of a line, a `\r\n` pair,
  4. //! or a multi-byte UTF-8 character. The parser buffers raw bytes and only
  5. //! decodes complete lines, so every way of chunking the same stream yields the
  6. //! same events (the property test below checks this).
  7. /// One dispatched event: its `event:` name, if any, and its joined `data:` lines.
  8. #[derive(Debug, Clone, PartialEq, Eq)]
  9. pub struct SseEvent {
  10. pub event: Option<String>,
  11. pub data: String,
  12. }
  13. #[derive(Debug, Default)]
  14. pub struct SseParser {
  15. buf: Vec<u8>,
  16. data: Vec<String>,
  17. event: Option<String>,
  18. /// A `\r` ended the last chunk; a `\n` at the start of the next one belongs to it.
  19. pending_cr: bool,
  20. }
  21. impl SseParser {
  22. pub fn new() -> Self {
  23. Self::default()
  24. }
  25. /// Feed bytes; returns every event completed by them.
  26. pub fn push(&mut self, chunk: &[u8]) -> Vec<SseEvent> {
  27. let mut out = Vec::new();
  28. let mut bytes = chunk;
  29. if self.pending_cr {
  30. self.pending_cr = false;
  31. if bytes.first() == Some(&b'\n') {
  32. bytes = &bytes[1..];
  33. }
  34. }
  35. self.buf.extend_from_slice(bytes);
  36. let mut start = 0;
  37. let mut i = 0;
  38. while i < self.buf.len() {
  39. match self.buf[i] {
  40. b'\n' => {
  41. let line = self.buf[start..i].to_vec();
  42. self.line(&line, &mut out);
  43. i += 1;
  44. start = i;
  45. }
  46. b'\r' => {
  47. let line = self.buf[start..i].to_vec();
  48. self.line(&line, &mut out);
  49. i += 1;
  50. if i < self.buf.len() {
  51. if self.buf[i] == b'\n' {
  52. i += 1;
  53. }
  54. } else {
  55. self.pending_cr = true;
  56. }
  57. start = i;
  58. }
  59. _ => i += 1,
  60. }
  61. }
  62. self.buf.drain(..start);
  63. out
  64. }
  65. /// End of stream: an event left without its blank line is still delivered.
  66. pub fn finish(&mut self) -> Vec<SseEvent> {
  67. let mut out = Vec::new();
  68. if !self.buf.is_empty() {
  69. let line = std::mem::take(&mut self.buf);
  70. self.line(&line, &mut out);
  71. }
  72. self.dispatch(&mut out);
  73. out
  74. }
  75. fn line(&mut self, raw: &[u8], out: &mut Vec<SseEvent>) {
  76. if raw.is_empty() {
  77. self.dispatch(out);
  78. return;
  79. }
  80. let line = String::from_utf8_lossy(raw);
  81. if line.starts_with(':') {
  82. return; // comment / keepalive
  83. }
  84. let (field, value) = match line.find(':') {
  85. Some(at) => {
  86. let v = &line[at + 1..];
  87. (&line[..at], v.strip_prefix(' ').unwrap_or(v))
  88. }
  89. None => (&line[..], ""),
  90. };
  91. match field {
  92. "data" => self.data.push(value.to_string()),
  93. "event" => self.event = Some(value.to_string()),
  94. _ => {} // id, retry and unknown fields are not used
  95. }
  96. }
  97. fn dispatch(&mut self, out: &mut Vec<SseEvent>) {
  98. if self.data.is_empty() {
  99. self.event = None;
  100. return;
  101. }
  102. out.push(SseEvent { event: self.event.take(), data: std::mem::take(&mut self.data).join("\n") });
  103. }
  104. }
  105. #[cfg(test)]
  106. mod tests {
  107. use super::*;
  108. use proptest::prelude::*;
  109. fn all(input: &[u8]) -> Vec<SseEvent> {
  110. let mut p = SseParser::new();
  111. let mut out = p.push(input);
  112. out.extend(p.finish());
  113. out
  114. }
  115. fn data(events: &[SseEvent]) -> Vec<&str> {
  116. events.iter().map(|e| e.data.as_str()).collect()
  117. }
  118. #[test]
  119. fn basic_lf_and_crlf() {
  120. assert_eq!(data(&all(b"data: a\n\ndata: b\n\n")), ["a", "b"]);
  121. assert_eq!(data(&all(b"data: a\r\n\r\ndata: b\r\n\r\n")), ["a", "b"]);
  122. assert_eq!(data(&all(b"data: a\r\rdata: b\r\r")), ["a", "b"]);
  123. }
  124. #[test]
  125. fn comments_multiline_and_event_names() {
  126. let ev = all(b": keepalive\n\nevent: error\ndata: one\ndata: two\n\ndata:x\n\n");
  127. assert_eq!(ev.len(), 2);
  128. assert_eq!(ev[0].event.as_deref(), Some("error"));
  129. assert_eq!(ev[0].data, "one\ntwo");
  130. assert_eq!(ev[1], SseEvent { event: None, data: "x".into() });
  131. }
  132. #[test]
  133. fn unterminated_final_event_is_delivered() {
  134. assert_eq!(data(&all(b"data: [DONE]")), ["[DONE]"]);
  135. }
  136. #[test]
  137. fn utf8_split_across_chunks() {
  138. let text = "data: caf\u{e9} \u{1F600}\n\n".as_bytes();
  139. let mut p = SseParser::new();
  140. let mut out = Vec::new();
  141. for b in text {
  142. out.extend(p.push(std::slice::from_ref(b)));
  143. }
  144. assert_eq!(out[0].data, "caf\u{e9} \u{1F600}");
  145. }
  146. #[test]
  147. fn crlf_split_between_chunks() {
  148. let mut p = SseParser::new();
  149. let mut out = p.push(b"data: a\r");
  150. out.extend(p.push(b"\n\r"));
  151. out.extend(p.push(b"\ndata: b\r\n\r\n"));
  152. assert_eq!(data(&out), ["a", "b"]);
  153. }
  154. proptest! {
  155. #[test]
  156. fn any_chunking_gives_the_same_events(cuts in proptest::collection::vec(0usize..400, 0..30)) {
  157. let stream = "data: {\"a\":1}\r\n\r\n: ping\n\nevent: x\ndata: l1\ndata: l2 \u{e9}\u{4e2d}\n\ndata: [DONE]\n\n".as_bytes();
  158. let expected = all(stream);
  159. let mut points: Vec<usize> = cuts.into_iter().map(|c| c % (stream.len() + 1)).collect();
  160. points.sort();
  161. let mut p = SseParser::new();
  162. let mut got = Vec::new();
  163. let mut last = 0;
  164. for at in points {
  165. got.extend(p.push(&stream[last..at]));
  166. last = at;
  167. }
  168. got.extend(p.push(&stream[last..]));
  169. got.extend(p.finish());
  170. prop_assert_eq!(got, expected);
  171. }
  172. }
  173. }