Skip to main content

Module streaming

Module streaming 

Source
Expand description

Streaming ResponseWriter helpers for actix-web (or any) HTTP host.

webui-handler writes through a push-based ResponseWriter trait — every Raw fragment, attribute, signal value, route element open/close, CSS preload <link>, and template assignment is a separate write() call (~hundreds per render). The default host pattern collects them all into a String, then serves the whole HTML body in one shot — which delays first-byte until the entire render finishes and forces the browser to wait for everything before parsing.

The helpers here let a host flush bytes to the network as soon as they’re written:

  • StreamingWriter — coalesces small writes into ~4 KB chunks and pushes them through a bounded tokio::sync::mpsc::Sender. The bound (DEFAULT_CHANNEL_CAPACITY = 4 chunks ≈ 16 KB) provides backpressure: when a slow client cannot keep up, the producer parks on the channel until the receiver drains, instead of queuing the entire response in memory. A configurable flush deadline (via with_flush_timeout) caps the maximum time a producer thread can be parked, bounding the slow-loris DoS surface to timeout × concurrent_renders. When the receiver is dropped (client disconnect) or the deadline elapses, write returns a typed error so the handler aborts instead of doing wasted CPU work.

  • ChunkPool — lock-free shared pool of chunk buffers. Used via StreamingWriter::new_pooled to recycle the per-flush Vec<u8> across requests, eliminating per-flush heap allocation in steady-state high-RPS workloads.

Hot-path allocation profile:

  • StreamingWriter::new() (unpooled): one Vec::reserve per ~4 KB flush (the previous buffer is moved zero-copy into bytes::Bytes when len < cap; when len == cap, Bytes::from(Vec) is still a move via into_boxed_slice). Plus one small Box<Shared> for the refcount metadata.
  • StreamingWriter::new_pooled(): zero per-flush heap allocation in steady state — chunk buffers come from the pool and return on Bytes drop. Single atomic CAS per acquire/release.

§Per-render HTML injection

Hosts that need to splice HTML at the structural </head> or </body> boundaries (image preload <link> tags, dev livereload <script>, CSP nonce reflections, analytics, etc.) should use RenderOptions::with_head_inject / RenderOptions::with_body_inject on the handler side — NOT a writer-level scanner. The parser already synthesises head_end / body_end signal fragments at the exact structural boundaries; the handler emits the inject HTML there with zero scan cost and no risk of mis-firing on </body> literals appearing inside HTML comments, <iframe srcdoc>, or inline scripts.

Structs§

ChunkPool
Lock-free shared pool of Vec<u8> buffers used to recycle chunk allocations across StreamingWriter instances.
StreamingWriter
Streaming ResponseWriter backed by a bounded tokio mpsc channel of Bytes.