use std::collections::VecDeque;
use std::time::Duration;
use std::time::Instant;
use futures_core::Stream;
use futures_util::StreamExt;
use crate::http::BodyStream;
use crate::http::TransportError;
pub const DEFAULT_MAX_EVENT_BYTES: usize = 16 * 1024 * 1024;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SseEvent {
pub event: Option<String>,
pub data: String,
pub id: Option<String>,
pub retry: Option<Duration>,
pub received_at: Option<Instant>,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum SseError {
#[error("event exceeds the limit of {limit} bytes")]
EventTooLarge {
limit: usize,
},
}
#[derive(Debug)]
pub struct SseDecoder {
buffer: Vec<u8>,
data: String,
event: String,
last_event_id: String,
retry: Option<Duration>,
bom_checked: bool,
pending_cr: bool,
max_event_bytes: usize,
}
impl Default for SseDecoder {
fn default() -> Self {
Self::new()
}
}
impl SseDecoder {
#[must_use]
pub fn new() -> Self {
Self {
buffer: Vec::new(),
data: String::new(),
event: String::new(),
last_event_id: String::new(),
retry: None,
bom_checked: false,
pending_cr: false,
max_event_bytes: DEFAULT_MAX_EVENT_BYTES,
}
}
#[must_use]
pub fn with_max_event_bytes(mut self, max_event_bytes: usize) -> Self {
self.max_event_bytes = max_event_bytes;
self
}
pub fn feed(&mut self, bytes: &[u8]) -> Result<Vec<SseEvent>, SseError> {
let mut events = Vec::new();
for &byte in bytes {
if !self.bom_checked {
const BOM: [u8; 3] = [0xEF, 0xBB, 0xBF];
if byte == BOM[self.buffer.len()] {
self.buffer.push(byte);
if self.buffer.len() == BOM.len() {
self.buffer.clear();
self.bom_checked = true;
}
continue;
}
self.bom_checked = true;
}
if self.pending_cr {
self.pending_cr = false;
if byte == b'\n' {
continue;
}
}
match byte {
b'\n' | b'\r' => {
self.pending_cr = byte == b'\r';
let line = std::mem::take(&mut self.buffer);
if let Some(event) = self.process_line(&line) {
events.push(event);
}
}
other => {
self.buffer.push(other);
if self.buffer.len() + self.data.len() > self.max_event_bytes {
return Err(SseError::EventTooLarge {
limit: self.max_event_bytes,
});
}
}
}
}
Ok(events)
}
pub fn finish(&mut self) {
self.buffer.clear();
self.data.clear();
self.event.clear();
self.pending_cr = false;
}
fn process_line(&mut self, line: &[u8]) -> Option<SseEvent> {
if line.is_empty() {
return self.dispatch();
}
if line[0] == b':' {
return None;
}
let text = String::from_utf8_lossy(line);
let (field, value) = match text.split_once(':') {
Some((field, value)) => (field, value.strip_prefix(' ').unwrap_or(value)),
None => (text.as_ref(), ""),
};
match field {
"event" => {
self.event.clear();
self.event.push_str(value);
}
"data" => {
self.data.push_str(value);
self.data.push('\n');
}
"id" => {
if !value.contains('\0') {
self.last_event_id.clear();
self.last_event_id.push_str(value);
}
}
"retry" if !value.is_empty() && value.bytes().all(|byte| byte.is_ascii_digit()) => {
self.retry = value.parse::<u64>().ok().map(Duration::from_millis);
}
_ => {}
}
None
}
fn dispatch(&mut self) -> Option<SseEvent> {
if self.data.is_empty() {
self.event.clear();
return None;
}
let mut data = std::mem::take(&mut self.data);
data.pop();
let event = std::mem::take(&mut self.event);
Some(SseEvent {
event: (!event.is_empty()).then_some(event),
data,
id: (!self.last_event_id.is_empty()).then(|| self.last_event_id.clone()),
retry: self.retry,
received_at: None,
})
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SseStreamError {
#[error(transparent)]
Transport(TransportError),
#[error(transparent)]
Decode(SseError),
}
pub fn decode_stream(
body: BodyStream,
max_event_bytes: usize,
) -> impl Stream<Item = Result<SseEvent, SseStreamError>> + Send + 'static {
struct State {
body: BodyStream,
decoder: SseDecoder,
pending: VecDeque<SseEvent>,
done: bool,
}
let state = State {
body,
decoder: SseDecoder::new().with_max_event_bytes(max_event_bytes),
pending: VecDeque::new(),
done: false,
};
futures_util::stream::unfold(state, |mut state| async move {
loop {
if let Some(event) = state.pending.pop_front() {
return Some((Ok(event), state));
}
if state.done {
return None;
}
match state.body.next().await {
Some(Ok(chunk)) => match state.decoder.feed(&chunk) {
Ok(events) => {
let now = Instant::now();
state.pending.extend(events.into_iter().map(|mut event| {
event.received_at = Some(now);
event
}));
}
Err(error) => {
state.done = true;
return Some((Err(SseStreamError::Decode(error)), state));
}
},
Some(Err(error)) => {
state.done = true;
return Some((Err(SseStreamError::Transport(error)), state));
}
None => {
state.done = true;
state.decoder.finish();
}
}
}
})
}