#[cfg(test)]
mod tests;
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, SyncSender, TrySendError};
use std::time::Duration;
use crate::core::New;
use crate::header::Header;
use crate::range::Range;
use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
pub struct SseEvent {
id: Option<String>,
event: Option<String>,
data: String,
retry_ms: Option<u32>,
}
impl SseEvent {
pub fn data(data: impl Into<String>) -> Self {
SseEvent { id: None, event: None, data: data.into(), retry_ms: None }
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn event_type(mut self, event: impl Into<String>) -> Self {
self.event = Some(event.into());
self
}
pub fn retry(mut self, ms: u32) -> Self {
self.retry_ms = Some(ms);
self
}
pub fn encode(&self) -> Vec<u8> {
let one_line = |value: &str| value.replace(['\r', '\n'], "");
let mut out = String::new();
if let Some(id) = &self.id {
out.push_str("id: ");
out.push_str(&one_line(id));
out.push('\n');
}
if let Some(event) = &self.event {
out.push_str("event: ");
out.push_str(&one_line(event));
out.push('\n');
}
for line in self.data.replace("\r\n", "\n").split(['\n', '\r']) {
out.push_str("data: ");
out.push_str(line);
out.push('\n');
}
if let Some(ms) = self.retry_ms {
out.push_str("retry: ");
out.push_str(&ms.to_string());
out.push('\n');
}
out.push('\n');
out.into_bytes()
}
}
pub struct Sse {
chunks: Vec<Vec<u8>>,
}
impl Sse {
pub fn new() -> Self {
Sse { chunks: Vec::new() }
}
pub fn event(mut self, event_type: &str, data: &str) -> Self {
self.chunks.push(
SseEvent::data(data).event_type(event_type).encode(),
);
self
}
pub fn data(mut self, data: &str) -> Self {
self.chunks.push(SseEvent::data(data).encode());
self
}
pub fn push(mut self, event: SseEvent) -> Self {
self.chunks.push(event.encode());
self
}
pub fn retry(mut self, ms: u32) -> Self {
self.chunks.push(format!("retry: {}\n\n", ms).into_bytes());
self
}
pub fn comment(mut self, text: &str) -> Self {
self.chunks.push(comment_bytes(text));
self
}
pub fn stream() -> (SseSender, Response) {
SseStream::new().start()
}
pub fn into_response(self) -> Response {
let body: Vec<u8> = self.chunks.into_iter().flatten().collect();
let mut response = Response::new();
response.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
response.reason_phrase =
STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
response.headers.push(Header {
name: Header::_CACHE_CONTROL.into(),
value: "no-cache".into(),
});
response.headers.push(Header {
name: "X-Accel-Buffering".into(),
value: "no".into(),
});
response.content_range_list = vec![Range::get_content_range(
body,
"text/event-stream".to_string(),
)];
response
}
}
fn comment_bytes(text: &str) -> Vec<u8> {
let mut out = String::new();
for line in text.replace("\r\n", "\n").split(['\n', '\r']) {
out.push_str(": ");
out.push_str(line);
out.push('\n');
}
out.push('\n');
out.into_bytes()
}
fn stream_response(body: Option<Box<dyn std::io::Read + Send>>) -> Response {
let mut response = Response::new();
response.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
response.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
response.headers.push(Header { name: Header::_CONTENT_TYPE.into(), value: "text/event-stream".into() });
response.headers.push(Header { name: Header::_CACHE_CONTROL.into(), value: "no-cache".into() });
response.headers.push(Header { name: "X-Accel-Buffering".into(), value: "no".into() });
response.stream_pipe = body;
response
}
pub struct SseStream {
buffer: usize,
keep_alive: Duration,
}
impl Default for SseStream {
fn default() -> Self {
SseStream::new()
}
}
impl SseStream {
pub fn new() -> Self {
SseStream { buffer: 32, keep_alive: Duration::from_secs(15) }
}
pub fn buffer(mut self, events: usize) -> Self {
self.buffer = events.max(1);
self
}
pub fn keep_alive(mut self, interval: Duration) -> Self {
self.keep_alive = interval.max(Duration::from_millis(10));
self
}
pub fn start(self) -> (SseSender, Response) {
let (tx, rx) = mpsc::sync_channel(self.buffer);
let response = stream_response(Some(Box::new(SseReader::new(rx, self.keep_alive))));
(SseSender { tx }, response)
}
}
#[derive(Clone)]
pub struct SseSender {
tx: SyncSender<Vec<u8>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SseClosed;
impl std::fmt::Display for SseClosed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("the SSE stream is closed")
}
}
impl std::error::Error for SseClosed {}
impl SseSender {
pub fn send(&self, event: SseEvent) -> Result<(), SseClosed> {
self.tx.send(event.encode()).map_err(|_| SseClosed)
}
pub fn try_send(&self, event: SseEvent) -> Result<bool, SseClosed> {
match self.tx.try_send(event.encode()) {
Ok(()) => Ok(true),
Err(TrySendError::Full(_)) => Ok(false),
Err(TrySendError::Disconnected(_)) => Err(SseClosed),
}
}
#[cfg(feature = "http2")]
pub async fn send_async(&self, event: SseEvent) -> Result<(), SseClosed> {
let mut frame = event.encode();
loop {
match self.tx.try_send(frame) {
Ok(()) => return Ok(()),
Err(TrySendError::Disconnected(_)) => return Err(SseClosed),
Err(TrySendError::Full(back)) => {
frame = back;
tokio::time::sleep(Duration::from_millis(5)).await;
}
}
}
}
pub fn data(&self, data: &str) -> Result<(), SseClosed> {
self.send(SseEvent::data(data))
}
pub fn comment(&self, text: &str) -> Result<(), SseClosed> {
self.tx.send(comment_bytes(text)).map_err(|_| SseClosed)
}
}
pub(crate) struct SseReader {
rx: Receiver<Vec<u8>>,
leftover: Vec<u8>,
offset: usize,
keep_alive: Duration,
}
impl SseReader {
pub(crate) fn new(rx: Receiver<Vec<u8>>, keep_alive: Duration) -> Self {
SseReader { rx, leftover: Vec::new(), offset: 0, keep_alive }
}
}
impl std::io::Read for SseReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.offset >= self.leftover.len() {
self.leftover = match self.rx.recv_timeout(self.keep_alive) {
Ok(frame) => frame,
Err(RecvTimeoutError::Timeout) => b": keep-alive\n\n".to_vec(),
Err(RecvTimeoutError::Disconnected) => return Ok(0),
};
self.offset = 0;
}
let n = (self.leftover.len() - self.offset).min(buf.len());
buf[..n].copy_from_slice(&self.leftover[self.offset..self.offset + n]);
self.offset += n;
Ok(n)
}
}