1use std::sync::Arc;
7
8use serde_json::Value;
9
10use super::session::{
11 BoundaryDescriptor, BoundaryInstanceId, BoundaryMode, SessionCall, SessionCore, StreamStatus,
12 StreamingState,
13};
14use super::StreamingSink;
15use crate::route_handler::Protocol;
16use crate::{FlushWriter, RenderOptions, ResponseWriter, Result, WebUIHandler};
17
18#[derive(Default)]
24pub struct BufferSink {
25 bytes: Vec<u8>,
26 last_flush: usize,
27}
28
29impl BufferSink {
30 fn reset(&mut self) {
31 self.bytes.clear();
32 self.last_flush = 0;
33 }
34}
35
36impl ResponseWriter for BufferSink {
37 fn write(&mut self, content: &str) -> Result<()> {
38 self.bytes.extend_from_slice(content.as_bytes());
39 Ok(())
40 }
41
42 fn write_attribute(&mut self, name: &str, value: &str) -> Result<()> {
43 crate::append_attribute_to_bytes(&mut self.bytes, name, value);
44 Ok(())
45 }
46
47 fn write_boolean_attribute(&mut self, name: &str) -> Result<()> {
48 crate::append_boolean_attribute_to_bytes(&mut self.bytes, name);
49 Ok(())
50 }
51
52 fn end(&mut self) -> Result<()> {
53 Ok(())
54 }
55}
56
57impl FlushWriter for BufferSink {
58 fn flush(&mut self) -> Result<()> {
59 self.last_flush = self.bytes.len();
60 Ok(())
61 }
62}
63
64#[derive(Clone, Debug)]
66pub struct SessionOptions {
67 pub entry_id: String,
69 pub request_path: String,
71 pub nonce: Option<String>,
73 pub head_inject: Option<String>,
75 pub body_inject: Option<String>,
77}
78
79impl SessionOptions {
80 #[must_use]
82 pub fn new(entry_id: impl Into<String>, request_path: impl Into<String>) -> Self {
83 Self {
84 entry_id: entry_id.into(),
85 request_path: request_path.into(),
86 nonce: None,
87 head_inject: None,
88 body_inject: None,
89 }
90 }
91}
92
93#[derive(Clone, Debug, Eq, PartialEq)]
100pub struct StreamStep {
101 pub bytes: Vec<u8>,
103 pub boundary: Option<BoundaryDescriptor>,
105 pub done: bool,
107}
108
109pub struct StreamingSession {
116 handler: Arc<WebUIHandler>,
117 protocol: Arc<Protocol>,
118 options: SessionOptions,
119 core: SessionCore,
120 sink: BufferSink,
121}
122
123impl StreamingSession {
124 pub fn new(
126 handler: Arc<WebUIHandler>,
127 protocol: Arc<Protocol>,
128 options: SessionOptions,
129 ) -> Result<Self> {
130 let core = SessionCore::new(&handler, &protocol, &options.entry_id)?;
131 Ok(Self {
132 handler,
133 protocol,
134 options,
135 core,
136 sink: BufferSink::default(),
137 })
138 }
139
140 pub fn start<'state>(
145 &mut self,
146 state: impl Into<StreamingState<'state>>,
147 ) -> Result<StreamStep> {
148 match state.into() {
149 StreamingState::Borrowed(state) => self.step(|core, call| core.start(call, state)),
150 StreamingState::Owned(state) => {
151 self.step(move |core, call| core.start_owned(call, state))
152 }
153 }
154 }
155
156 pub fn resume<'state>(
169 &mut self,
170 instance_id: BoundaryInstanceId,
171 state: impl Into<StreamingState<'state>>,
172 mode: BoundaryMode,
173 ) -> Result<StreamStep> {
174 match state.into() {
175 StreamingState::Borrowed(state) => {
176 self.step(|core, call| core.resume(call, instance_id, state, mode))
177 }
178 StreamingState::Owned(state) => {
179 self.step(move |core, call| core.resume_owned(call, instance_id, state, mode))
180 }
181 }
182 }
183
184 pub fn resume_current(
190 &mut self,
191 instance_id: BoundaryInstanceId,
192 mode: BoundaryMode,
193 ) -> Result<StreamStep> {
194 self.step(|core, call| core.resume_current(call, instance_id, mode))
195 }
196
197 pub fn advance(&mut self) -> Result<StreamStep> {
201 self.step(SessionCore::advance)
202 }
203
204 pub fn update(&mut self, instance_id: BoundaryInstanceId, patch: &Value) -> Result<Vec<u8>> {
209 self.sink.reset();
210 let options = render_options(&self.options);
211 let mut sink = StreamingSink {
212 transport: &mut self.sink,
213 component_opening: None,
214 written: 0,
215 flushed: 0,
216 };
217 let result = self.core.update(
218 SessionCall {
219 handler: &self.handler,
220 protocol: &self.protocol,
221 options: &options,
222 writer: &mut sink,
223 },
224 instance_id,
225 patch,
226 );
227 match result {
228 Ok(()) => {
229 verify_flush_boundary(&self.sink)?;
230 Ok(std::mem::take(&mut self.sink.bytes))
231 }
232 Err(error) => {
233 self.sink.reset();
234 Err(error)
235 }
236 }
237 }
238
239 #[must_use]
241 pub fn is_done(&self) -> bool {
242 self.core.done
243 }
244
245 fn step(
246 &mut self,
247 operation: impl FnOnce(&mut SessionCore, SessionCall<'_, '_>) -> Result<StreamStatus>,
248 ) -> Result<StreamStep> {
249 self.sink.reset();
250 let options = render_options(&self.options);
251 let mut sink = StreamingSink {
252 transport: &mut self.sink,
253 component_opening: None,
254 written: 0,
255 flushed: 0,
256 };
257 let status = operation(
258 &mut self.core,
259 SessionCall {
260 handler: &self.handler,
261 protocol: &self.protocol,
262 options: &options,
263 writer: &mut sink,
264 },
265 );
266 match status {
267 Ok(status) => {
268 verify_flush_boundary(&self.sink)?;
269 Ok(StreamStep {
270 bytes: std::mem::take(&mut self.sink.bytes),
271 boundary: status.boundary,
272 done: status.done,
273 })
274 }
275 Err(error) => {
276 self.sink.reset();
277 Err(error)
278 }
279 }
280 }
281}
282
283fn verify_flush_boundary(sink: &BufferSink) -> Result<()> {
290 if sink.last_flush == sink.bytes.len() {
291 return Ok(());
292 }
293 Err(unflushed_step_error())
294}
295
296#[cold]
297#[inline(never)]
298fn unflushed_step_error() -> crate::HandlerError {
299 crate::HandlerError::Invariant(
300 "a streaming step buffered bytes past its last flush boundary".to_string(),
301 )
302}
303
304fn render_options(options: &SessionOptions) -> RenderOptions<'_> {
305 RenderOptions {
306 entry_id: &options.entry_id,
307 request_path: &options.request_path,
308 nonce: options.nonce.as_deref(),
309 head_inject: options.head_inject.as_deref(),
310 body_inject: options.body_inject.as_deref(),
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use std::sync::{Arc, Mutex};
317
318 use serde_json::Value;
319
320 use super::{BufferSink, SessionOptions, StreamingSession};
321 use crate::route_handler::Protocol;
322 use crate::{BoundaryMode, FlushWriter, ResponseWriter, WebUIHandler};
323
324 #[test]
325 fn owned_streaming_session_supports_synchronized_hosts() {
326 fn assert_send<T: Send>() {}
327 fn assert_send_sync<T: Send + Sync>() {}
328
329 assert_send::<StreamingSession>();
330 assert_send_sync::<Mutex<StreamingSession>>();
331 assert_send_sync::<Arc<WebUIHandler>>();
332 }
333
334 #[test]
335 fn buffer_sink_tracks_the_transport_flush_boundary() {
336 let mut sink = BufferSink::default();
340 sink.write("checkpoint").expect("write");
341 sink.flush().expect("flush");
342 assert!(super::verify_flush_boundary(&sink).is_ok());
343 sink.write("tail").expect("write");
344 assert!(super::verify_flush_boundary(&sink).is_err());
345 sink.reset();
346 assert!(super::verify_flush_boundary(&sink).is_ok());
347 }
348
349 #[test]
350 fn every_owned_step_ends_on_a_flush_boundary() {
351 let mut parser = webui_parser::HtmlParser::new();
352 parser
353 .parse(
354 "index.html",
355 concat!(
356 "<html><head></head><body>",
357 r#"<boundary name="first"><p>1</p></boundary>"#,
358 "<hr>",
359 r#"<boundary name="second"><p>2</p></boundary>"#,
360 "<footer>tail</footer></body></html>",
361 ),
362 )
363 .expect("parse");
364 let protocol = Arc::new(Protocol::new(webui_protocol::WebUIProtocol::new(
365 parser.into_fragment_records(),
366 )));
367 let mut session = StreamingSession::new(
368 Arc::new(WebUIHandler::new()),
369 protocol,
370 SessionOptions::new("index.html", "/"),
371 )
372 .expect("session");
373
374 let state = Arc::new(Value::Object(serde_json::Map::new()));
375 let mut step = session.start(&state).expect("start");
376 let mut steps = 1usize;
377 while !step.done {
378 step = match step.boundary.as_ref() {
379 Some(boundary) => session
380 .resume(boundary.instance_id, &state, BoundaryMode::Final)
381 .expect("resume"),
382 None => session.advance().expect("advance"),
383 };
384 steps += 1;
385 }
386 assert_eq!(steps, 5);
388 assert!(session.is_done());
389 }
390}