Skip to main content

Server

Struct Server 

Source
pub struct Server { /* private fields */ }
Expand description

The main Actus server.

Implementations§

Source§

impl Server

Source

pub fn new(router: Router) -> Self

Create a server for router with default settings: no middleware, no CORS, the default body cap, and no DoS limits. Configure it with the with_* builder methods, then call run.

Source

pub fn with_middleware(self, middleware: impl Middleware + 'static) -> Self

Adds a middleware to the server’s request processing chain.

Source

pub fn with_cors(self, cors: CorsLayer) -> Self

Enables CORS with the given policy. The server then answers preflight (OPTIONS) requests itself and adds the Access-Control-* headers to every cross-origin response (including error responses). See CorsLayer.

Source

pub fn with_compression(self, layer: CompressionLayer) -> Self

Enables response compression (gzip / brotli). For each response Actus picks an encoding from the request’s Accept-Encoding and compresses buffered, compressible bodies above the layer’s size threshold. See CompressionLayer. (Requires the compression feature.)

Source

pub fn with_max_body_bytes(self, max: usize) -> Self

Caps the request body Actus will buffer (default DEFAULT_MAX_BODY_BYTES = 2 MiB). A larger body is rejected with 413 Payload Too Large before it can exhaust memory — the limit bounds buffered bytes, so it also covers chunked bodies that lie about (or omit) Content-Length.

0 is accepted and means “reject every non-empty body” — typically only useful on a strictly-GET surface that should never see a body.

Source

pub fn with_request_timeout(self, d: Duration) -> Self

Cap the total time any single request may take — body parse, middleware before, handler, middleware after, and finalization combined. An over-budget request is aborted (the handler’s future is dropped) and the client gets 504 Gateway Timeout. No timeout is set by default.

Scope. The timer covers the request/response exchange. A WebSocket upgrade succeeds inside the timer (the 101 is the response); the post-upgrade conversation runs in its own task and is not bound by this timeout.

Effect of an over-budget request. When the timer elapses the in-flight future is dropped, which cancels whatever the handler was awaiting (DB query, channel recv, etc.). The 504 reply is one-shot — the after-chain doesn’t run on it (by definition, some component upstream was unresponsive; running more risks hanging again).

Source

pub fn with_drain_deadline(self, d: Duration) -> Self

Override the grace period for in-flight connections after a shutdown signal (default DEFAULT_DRAIN_DEADLINE = 30 s). Anything still running at the deadline is hard-aborted via JoinSet::shutdown. Use a longer value for surfaces that hold long-lived connections (large file downloads, WebSockets); a shorter value for fast-iteration dev workflows. Duration::ZERO aborts every in-flight task immediately.

Source

pub fn with_max_connections(self, n: usize) -> Self

Cap concurrent connection tasks at n. While the cap is held, the accept loop pauses on permit acquisition; new SYNs queue in the kernel’s accept backlog and (once that fills, governed by SOMAXCONN) get dropped at the OS level. No userland reject / no-503-per-conn cost — the kernel handles the spillover.

Each connection task holds its permit until it ends, including the post-handshake WebSocket conversation. Size accordingly: a with_max_connections(N) server can hold N long-lived WebSockets before it stops accepting new connections of any kind.

Unbounded by default (no semaphore installed).

Source

pub fn with_max_inflight_body_bytes(self, n: usize) -> Self

Cap the total bytes being buffered across all in-flight body reads at n. Each request reserves its per-request cap (see with_max_body_bytes) from this global budget upfront; if the budget is exhausted, the request is refused with 503 Service Unavailable (via WebError::Busy) and a short Retry-After.

Together with Self::with_max_connections this puts a hard ceiling on the framework’s memory under adversarial load: with_max_connections(C) * with_max_body_bytes(B) is the worst case absent this knob; with it, the ceiling is min(C * B, this value).

Pre-reserving the per-request cap over-counts (a 1 KB request reserves up to its full cap); the alternative — incremental per-chunk byte accounting — is more code for the same effective ceiling, and a request that has already started buffering can’t be sensibly aborted partway through anyway.

n is clamped to u32::MAX internally (Tokio’s Semaphore permit count uses u32); for practical deployments this is no limit (4 GiB).

Unbounded by default.

Source

pub fn with_header_read_timeout(self, d: Duration) -> Self

Bound how long after starting to read request headers we’ll wait before dropping the connection. Forwards to hyper’s http1::Builder::header_read_timeout. Catches slowloris (sending headers one byte at a time) and clients that TCP-connect-and-send- nothing — the most common file-descriptor-exhaustion attack on a keep-alive HTTP server.

Note: hyper 1.x doesn’t have a separate “idle between requests” timeout (after a complete request, an idle keep-alive connection stays open until either side closes or the OS-level TCP keep-alive fires). If that matters for your deployment, either disable keep-alive entirely upstream of Actus or rely on the OS knobs.

No timeout by default (hyper’s default).

Source

pub async fn run(self, port: u16) -> Result<(), ServerError>

Runs the server on 127.0.0.1:port (loopback only). For a different bind address — e.g. 0.0.0.0:port to accept connections from other hosts in a container — use Server::run_on.

Listens for SIGTERM/SIGINT (Unix) or Ctrl-C (cross-platform) and shuts down gracefully: stops accepting new connections, signals in-flight connections to finish, and waits up to 30 seconds for them to drain before returning.

Source

pub async fn run_on(self, addr: SocketAddr) -> Result<(), ServerError>

Like Server::run but binds an arbitrary address. Pass 0.0.0.0:port (or [::]:port) to accept connections from other hosts.

Source

pub async fn run_with_shutdown( self, port: u16, shutdown: impl Future<Output = ()> + Send + 'static, ) -> Result<(), ServerError>

Like Server::run but with a custom shutdown trigger (a future that, when it resolves, starts the graceful drain). Binds 127.0.0.1:port; see Server::run_with_shutdown_on for a custom bind address. Useful for tests or for embedding the server in a larger supervision tree.

Source

pub async fn run_with_shutdown_on( self, addr: SocketAddr, shutdown: impl Future<Output = ()> + Send + 'static, ) -> Result<(), ServerError>

Bind addr, then serve exactly as Server::run_with_shutdown_listener — the general form — does. Server::run, Server::run_on, and Server::run_with_shutdown are thin wrappers over this; this is a thin bind over the listener form.

Drain bound. Once shutdown resolves the server stops accepting and signals every in-flight connection to wind down. The drain deadline defaults to DEFAULT_DRAIN_DEADLINE (30 s); override with Server::with_drain_deadline. Anything still running at the deadline is hard-aborted via JoinSet::shutdown. In particular, long-lived connections (WebSockets, slow downloads, kept-alive idle clients) and any connection task that raced the shutdown notification and missed it both get aborted at the deadline rather than draining gracefully.

Source

pub async fn run_listener( self, listener: TcpListener, ) -> Result<(), ServerError>

Like Server::run_on but serves on a listener the caller already bound (or inherited), with the default SIGTERM / SIGINT shutdown trigger. See Server::run_with_shutdown_listener for what a pre-bound listener buys and how to adopt an inherited one.

Source

pub async fn run_with_shutdown_listener( self, listener: TcpListener, shutdown: impl Future<Output = ()> + Send + 'static, ) -> Result<(), ServerError>

The most general form: serve on a listener the caller already bound (or inherited) until shutdown resolves, then drain. Every other run* method is a wrapper over this one.

Why hand the server a listener. Two cases the bind-it-yourself forms can’t express:

  • Socket activation — a supervisor (systemd’s LISTEN_FDS protocol, launchd, …) owns the listening socket and passes it to the process it spawns. Because the socket outlives the process, connections arriving during a restart queue in the kernel’s accept backlog instead of being refused, and the next process serves them.
  • Race-free embedding and tests — bind 127.0.0.1:0, keep the listener, and pass it in; no bind-drop-rebind window in which the port can be lost, and requests may connect before the accept loop even starts (the kernel queues them).

An inherited std::net::TcpListener must be set non-blocking before conversion — tokio requires it, and a supervisor passes the fd blocking:

inherited.set_nonblocking(true)?;
let listener = tokio::net::TcpListener::from_std(inherited)?;
server.run_with_shutdown_listener(listener, std::future::pending()).await

Drain bound. As Server::run_with_shutdown_on: once shutdown resolves the server stops accepting (the listener is dropped — under socket activation the socket stays open in the supervisor, which is the point) and in-flight connections get Server::with_drain_deadline (default DEFAULT_DRAIN_DEADLINE = 30 s) to finish before being aborted. A streaming response that never completes (SSE) always rides to the deadline.

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> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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> 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 = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more