1use std::sync::Arc;
7
8use serde_json::Value;
9
10use super::session::{
11 BoundaryDescriptor, BoundaryInstanceId, BoundaryMode, SessionCall, SessionCore, StreamStatus,
12};
13use super::StreamingSink;
14use crate::route_handler::Protocol;
15use crate::{FlushWriter, RenderOptions, ResponseWriter, Result, WebUIHandler};
16
17#[derive(Default)]
23pub struct BufferSink {
24 bytes: Vec<u8>,
25 last_flush: usize,
26}
27
28impl BufferSink {
29 fn reset(&mut self) {
30 self.bytes.clear();
31 self.last_flush = 0;
32 }
33}
34
35impl ResponseWriter for BufferSink {
36 fn write(&mut self, content: &str) -> Result<()> {
37 self.bytes.extend_from_slice(content.as_bytes());
38 Ok(())
39 }
40
41 fn write_attribute(&mut self, name: &str, value: &str) -> Result<()> {
42 crate::append_attribute_to_bytes(&mut self.bytes, name, value);
43 Ok(())
44 }
45
46 fn write_boolean_attribute(&mut self, name: &str) -> Result<()> {
47 crate::append_boolean_attribute_to_bytes(&mut self.bytes, name);
48 Ok(())
49 }
50
51 fn end(&mut self) -> Result<()> {
52 Ok(())
53 }
54}
55
56impl FlushWriter for BufferSink {
57 fn flush(&mut self) -> Result<()> {
58 self.last_flush = self.bytes.len();
59 Ok(())
60 }
61}
62
63#[derive(Clone, Debug)]
65pub struct SessionOptions {
66 pub entry_id: String,
68 pub request_path: String,
70 pub nonce: Option<String>,
72 pub head_inject: Option<String>,
74 pub body_inject: Option<String>,
76}
77
78impl SessionOptions {
79 #[must_use]
81 pub fn new(entry_id: impl Into<String>, request_path: impl Into<String>) -> Self {
82 Self {
83 entry_id: entry_id.into(),
84 request_path: request_path.into(),
85 nonce: None,
86 head_inject: None,
87 body_inject: None,
88 }
89 }
90}
91
92#[derive(Clone, Debug, Eq, PartialEq)]
99pub struct StreamStep {
100 pub bytes: Vec<u8>,
102 pub boundary: Option<BoundaryDescriptor>,
104 pub done: bool,
106}
107
108pub struct StreamingSession {
115 handler: Arc<WebUIHandler>,
116 protocol: Arc<Protocol>,
117 options: SessionOptions,
118 core: SessionCore,
119 sink: BufferSink,
120}
121
122impl StreamingSession {
123 pub fn new(
125 handler: Arc<WebUIHandler>,
126 protocol: Arc<Protocol>,
127 options: SessionOptions,
128 ) -> Result<Self> {
129 let core = SessionCore::new(&handler, &protocol, &options.entry_id)?;
130 Ok(Self {
131 handler,
132 protocol,
133 options,
134 core,
135 sink: BufferSink::default(),
136 })
137 }
138
139 pub fn start(&mut self, state: &Value) -> Result<StreamStep> {
141 self.step(|core, call| core.start(call, state))
142 }
143
144 pub fn resume(
154 &mut self,
155 instance_id: BoundaryInstanceId,
156 state: &Value,
157 mode: BoundaryMode,
158 ) -> Result<StreamStep> {
159 self.step(|core, call| core.resume(call, instance_id, state, mode))
160 }
161
162 pub fn advance(&mut self) -> Result<StreamStep> {
166 self.step(SessionCore::advance)
167 }
168
169 pub fn update(&mut self, instance_id: BoundaryInstanceId, patch: &Value) -> Result<Vec<u8>> {
174 self.sink.reset();
175 let options = render_options(&self.options);
176 let mut sink = StreamingSink {
177 transport: &mut self.sink,
178 component_opening: None,
179 written: 0,
180 flushed: 0,
181 };
182 let result = self.core.update(
183 SessionCall {
184 handler: &self.handler,
185 protocol: &self.protocol,
186 options: &options,
187 writer: &mut sink,
188 },
189 instance_id,
190 patch,
191 );
192 match result {
193 Ok(()) => {
194 verify_flush_boundary(&self.sink)?;
195 Ok(std::mem::take(&mut self.sink.bytes))
196 }
197 Err(error) => {
198 self.sink.reset();
199 Err(error)
200 }
201 }
202 }
203
204 #[must_use]
206 pub fn is_done(&self) -> bool {
207 self.core.done
208 }
209
210 fn step(
211 &mut self,
212 operation: impl FnOnce(&mut SessionCore, SessionCall<'_, '_>) -> Result<StreamStatus>,
213 ) -> Result<StreamStep> {
214 self.sink.reset();
215 let options = render_options(&self.options);
216 let mut sink = StreamingSink {
217 transport: &mut self.sink,
218 component_opening: None,
219 written: 0,
220 flushed: 0,
221 };
222 let status = operation(
223 &mut self.core,
224 SessionCall {
225 handler: &self.handler,
226 protocol: &self.protocol,
227 options: &options,
228 writer: &mut sink,
229 },
230 );
231 match status {
232 Ok(status) => {
233 verify_flush_boundary(&self.sink)?;
234 Ok(StreamStep {
235 bytes: std::mem::take(&mut self.sink.bytes),
236 boundary: status.boundary,
237 done: status.done,
238 })
239 }
240 Err(error) => {
241 self.sink.reset();
242 Err(error)
243 }
244 }
245 }
246}
247
248fn verify_flush_boundary(sink: &BufferSink) -> Result<()> {
255 if sink.last_flush == sink.bytes.len() {
256 return Ok(());
257 }
258 Err(unflushed_step_error())
259}
260
261#[cold]
262#[inline(never)]
263fn unflushed_step_error() -> crate::HandlerError {
264 crate::HandlerError::Invariant(
265 "a streaming step buffered bytes past its last flush boundary".to_string(),
266 )
267}
268
269fn render_options(options: &SessionOptions) -> RenderOptions<'_> {
270 RenderOptions {
271 entry_id: &options.entry_id,
272 request_path: &options.request_path,
273 nonce: options.nonce.as_deref(),
274 head_inject: options.head_inject.as_deref(),
275 body_inject: options.body_inject.as_deref(),
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use std::sync::{Arc, Mutex};
282
283 use serde_json::Value;
284
285 use super::{BufferSink, SessionOptions, StreamingSession};
286 use crate::route_handler::Protocol;
287 use crate::{BoundaryMode, FlushWriter, ResponseWriter, WebUIHandler};
288
289 #[test]
290 fn owned_streaming_session_supports_synchronized_hosts() {
291 fn assert_send<T: Send>() {}
292 fn assert_send_sync<T: Send + Sync>() {}
293
294 assert_send::<StreamingSession>();
295 assert_send_sync::<Mutex<StreamingSession>>();
296 assert_send_sync::<Arc<WebUIHandler>>();
297 }
298
299 #[test]
300 fn buffer_sink_tracks_the_transport_flush_boundary() {
301 let mut sink = BufferSink::default();
305 sink.write("checkpoint").expect("write");
306 sink.flush().expect("flush");
307 assert!(super::verify_flush_boundary(&sink).is_ok());
308 sink.write("tail").expect("write");
309 assert!(super::verify_flush_boundary(&sink).is_err());
310 sink.reset();
311 assert!(super::verify_flush_boundary(&sink).is_ok());
312 }
313
314 #[test]
315 fn every_owned_step_ends_on_a_flush_boundary() {
316 let mut parser = webui_parser::HtmlParser::new();
317 parser
318 .parse(
319 "index.html",
320 concat!(
321 "<html><head></head><body>",
322 r#"<boundary name="first"><p>1</p></boundary>"#,
323 "<hr>",
324 r#"<boundary name="second"><p>2</p></boundary>"#,
325 "<footer>tail</footer></body></html>",
326 ),
327 )
328 .expect("parse");
329 let protocol = Arc::new(Protocol::new(webui_protocol::WebUIProtocol::new(
330 parser.into_fragment_records(),
331 )));
332 let mut session = StreamingSession::new(
333 Arc::new(WebUIHandler::new()),
334 protocol,
335 SessionOptions::new("index.html", "/"),
336 )
337 .expect("session");
338
339 let state = Value::Object(serde_json::Map::new());
340 let mut step = session.start(&state).expect("start");
341 let mut steps = 1usize;
342 while !step.done {
343 step = match step.boundary.as_ref() {
344 Some(boundary) => session
345 .resume(boundary.instance_id, &state, BoundaryMode::Final)
346 .expect("resume"),
347 None => session.advance().expect("advance"),
348 };
349 steps += 1;
350 }
351 assert_eq!(steps, 5);
353 assert!(session.is_done());
354 }
355}