pub struct StreamingWriter { /* private fields */ }Expand description
Streaming ResponseWriter backed by a bounded tokio mpsc channel
of Bytes.
Coalesces small writes into ~4 KB chunks before flushing. The
underlying channel has a small bound
(DEFAULT_CHANNEL_CAPACITY) so a
slow consumer naturally backpressures the producer — the render
thread parks instead of queuing the entire response in memory.
A flush deadline (with_flush_timeout)
caps the maximum time the producer thread will park per flush,
bounding the slow-loris DoS surface. When the receiver is dropped
(typically client disconnect) or the deadline is exceeded, subsequent
ResponseWriter::write calls return a typed error
(HandlerError::ClientDisconnected / HandlerError::StreamTimeout)
so the handler can short-circuit the render.
§Example
use std::time::Duration;
use tokio::sync::mpsc;
use webui::streaming::StreamingWriter;
let (tx, mut rx) = mpsc::channel(StreamingWriter::DEFAULT_CHANNEL_CAPACITY);
actix_web::rt::task::spawn_blocking(move || {
let mut writer = StreamingWriter::new(tx)
.with_flush_timeout(Duration::from_secs(30));
handler.handle(&protocol, &state, &opts, &mut writer);
let _ = ResponseWriter::end(&mut writer);
});
// … wrap rx in a Stream and pass to HttpResponse::streaming …Implementations§
Source§impl StreamingWriter
impl StreamingWriter
Sourcepub const CHUNK_TARGET: usize
pub const CHUNK_TARGET: usize
Default chunk-coalescing target. ~4 KB is a balance: large enough to amortise per-call channel + actix overhead, small enough that head/body content arrives before the body_end script.
Tunable via with_chunk_size.
Sourcepub const DEFAULT_CHANNEL_CAPACITY: usize = 4
pub const DEFAULT_CHANNEL_CAPACITY: usize = 4
Default bounded-channel capacity in chunks. With
CHUNK_TARGET = 4 KB, this caps in-flight memory at ~16 KB per
in-progress request.
Sourcepub fn new(tx: Sender<Bytes>) -> Self
pub fn new(tx: Sender<Bytes>) -> Self
Wrap a tokio mpsc sender. Each render allocates its own chunk
buffers via the system allocator. For pooled allocation across
requests, use new_pooled.
Sourcepub fn new_pooled(tx: Sender<Bytes>, pool: Arc<ChunkPool>) -> Self
pub fn new_pooled(tx: Sender<Bytes>, pool: Arc<ChunkPool>) -> Self
Wrap a tokio mpsc sender, drawing chunk buffers from the
shared pool. Recycled buffers eliminate per-flush allocation
in steady-state high-RPS workloads. The pool is shared via
Arc and is safe to use from any number of concurrent
StreamingWriter instances; release happens when the consumer
drops the Bytes, on whichever thread held the last reference.
chunk_target defaults to CHUNK_TARGET;
override with with_chunk_size. When
the pool’s chunk size disagrees with the writer’s target, the
writer grows the acquired buffer on first use (one-time cost).
Sourcepub fn with_chunk_size(self, bytes: usize) -> Self
pub fn with_chunk_size(self, bytes: usize) -> Self
Override the chunk-coalescing target. Larger chunks reduce channel + syscall overhead at the cost of higher first-byte latency. Values below 64 bytes are silently raised to 64.
Common sizes:
- 1 KB: minimise TTFB on small pages.
- 4 KB (default): balanced for ~24 KB SSR pages.
- 16 KB: match TLS record size for large SSR (>200 KB).
Sourcepub fn with_flush_timeout(self, timeout: Duration) -> Self
pub fn with_flush_timeout(self, timeout: Duration) -> Self
Cap the maximum time a flush may park on the channel before
returning HandlerError::StreamTimeout. None (default) means
flushes block indefinitely on slow consumers.
Production HTTP hosts should set this (e.g. 30 s) so a single slow-loris client cannot pin a render thread forever. The chosen timeout × concurrent-render-limit is the upper bound on resources an attacker can pin.
Requires an active tokio runtime to be in TLS (i.e. the writer
is being driven from a spawn_blocking task on a tokio runtime).
Without one, the timeout is silently ignored and a plain
blocking_send is performed.
Sourcepub fn is_terminated(&self) -> bool
pub fn is_terminated(&self) -> bool
True after the writer has been terminated by a disconnect or flush timeout.