Skip to main content

StreamingWriter

Struct StreamingWriter 

Source
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.render(&protocol, &state, &opts, &mut writer);
    let _ = ResponseWriter::end(&mut writer);
});
// … wrap rx in a Stream and pass to HttpResponse::streaming …

Implementations§

Source§

impl StreamingWriter

Source

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.

Source

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.

Source

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.

Source

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).

Source

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).
Source

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.

Source

pub fn is_terminated(&self) -> bool

True after the writer has been terminated by a disconnect or flush timeout.

Trait Implementations§

Source§

impl Drop for StreamingWriter

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl FlushWriter for StreamingWriter

Source§

fn flush(&mut self) -> Result<()>

Hand all currently buffered bytes to the underlying transport.
Source§

impl ResponseWriter for StreamingWriter

Source§

fn write(&mut self, content: &str) -> Result<()>

Write content to the output
Source§

fn end(&mut self) -> Result<()>

Finalize the output

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.