Skip to main content

AppBuilder

Struct AppBuilder 

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

The fluent builder for an application, returned by Churust::server.

Chain DSL methods to configure the server (host, port, tls, …), register shared state, install plugins, and define routes with routing. Finish with build to get an App, or start to build and serve in one step. DSL setters take precedence over a with_config applied earlier.

use churust_core::{Churust, Call, TestClient};
let app = Churust::server()
    .port(3000)
    .routing(|r| { r.get("/", |_c: Call| async { "hi" }); })
    .build();
assert_eq!(app.config().port, 3000);
let res = TestClient::new(app).get("/").send().await;
assert_eq!(res.text(), "hi");

Implementations§

Source§

impl AppBuilder

Source

pub fn host(self, host: impl Into<String>) -> Self

Set the bind host (default "127.0.0.1"). Returns self for chaining.

Source

pub fn port(self, port: u16) -> Self

Set the bind port (default 8080). Returns self for chaining.

Source

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

Set the maximum accepted request body size in bytes (default 1 MiB). Larger bodies are rejected with 413 Payload Too Large. Returns self for chaining.

Source

pub fn with_config(self, cfg: Config) -> Self

Apply a fully-resolved Config, overwriting the current server settings.

This is lowest precedence: DSL setters called after it (e.g. another port) win. Use it to seed the builder from a config file/env, then override specific fields in code. Returns self for chaining.

Source

pub fn request_timeout_ms(self, ms: u64) -> Self

Set the per-request timeout in milliseconds (default 30000). A value of 0 disables the timeout. Requests exceeding it get 408 Request Timeout. Returns self for chaining.

Source

pub fn header_read_timeout_ms(self, ms: u64) -> Self

Set how long a client may take to send its complete header block (default 10000 ms; 0 disables).

This is the slow-loris defence. The per-request timeout cannot cover it, because there is no request until the headers arrive.

Source

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

Set the maximum number of request headers accepted (default 100).

Source

pub fn keep_alive_ms(self, ms: u64) -> Self

Set how long an idle connection is kept for reuse (default 75000 ms; 0 disables keep-alive and closes after each response).

A connection with a request in flight is not idle, however long the handler takes. Lower this when connection count matters more than round-trip latency; raise it for chatty clients on slow links.

Source

pub fn backlog(self, n: u32) -> Self

Set the listen backlog (default 1024).

Source

pub fn shutdown_timeout_ms(self, ms: u64) -> Self

Set how long graceful shutdown waits for in-flight requests (default 30000 ms; 0 waits indefinitely).

Unbounded waiting means one slow request can delay shutdown forever, which in a container means being killed rather than exiting cleanly.

Source

pub fn path_policy(self, policy: PathPolicy) -> Self

Set what happens to a non-canonical path spelling (default PathPolicy::Strict).

//a, /a//b and /a/ are aliases of /a. Serving them silently gives one resource several URLs, which makes prefix-based checks bypassable and cache identity ambiguous.

Source

pub fn h2_max_header_list_size(self, n: u32) -> Self

Set the maximum size of a received HTTP/2 header block in bytes (default 16384).

max_headers configures HTTP/1 only — it counts headers, and HTTP/2 has no equivalent count, only an encoded size. Set both if you serve both.

Source

pub fn h2_max_concurrent_streams(self, n: u32) -> Self

Set the maximum concurrent HTTP/2 streams per connection (default 200; 0 removes the limit).

An h2 connection multiplexes many requests, so this is what stops one connection from becoming an unbounded amount of concurrent work.

Source

pub fn ws_idle_timeout_ms(self, ms: u64) -> Self

Set how long an upgraded WebSocket may sit idle before it is closed (default 300000 ms; 0 disables the bound).

An upgraded socket holds a connection permit for its whole life, and no HTTP-level timeout survives the upgrade — so without this a peer that completes the handshake and then goes silent holds a permit until the process restarts. Idle means no frame in either direction.

Source

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

Set the maximum number of simultaneously served connections (default 25000; 0 means unlimited).

The backlog bounds what the kernel queues before the accept loop reaches it; this bounds what the process serves at once. Excess connections wait for a slot rather than being accepted, so the pressure shows up as latency instead of as an out-of-memory or out-of-descriptors death.

Source

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

Set the maximum number of TLS handshakes in progress at once (default 256; 0 means unlimited).

Deliberately far below max_connections: a handshake is asymmetric work, cheap for the client to request and expensive for the server to perform, so it gets its own tighter bound.

Source

pub fn tls_handshake_timeout_ms(self, ms: u64) -> Self

Set how long a TLS handshake may take before the connection is dropped (default 10000 ms; 0 disables the bound).

header_read_timeout_ms cannot cover this: until the handshake finishes there is no HTTP layer to time out. Without it, a client that completes the TCP handshake and then dribbles bytes holds a connection open indefinitely.

Source

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

Set the maximum number of path segments accepted (default 64). Longer paths are rejected with 414 URI Too Long.

Source

pub fn tls( self, cert_path: impl Into<String>, key_path: impl Into<String>, ) -> Self

Enable TLS, reading the certificate chain from cert_path and the private key from key_path (PEM). The files are loaded when the server starts; this only records the paths. Requires the tls feature to have any effect at serve time. Returns self for chaining.

Source

pub fn state<T: Send + Sync + 'static>(self, value: T) -> Self

Register a shared application-state value of type T, retrieved later via the State extractor or Call::state. One value is held per type; registering another T replaces it. Returns self for chaining.

use churust_core::{Churust, State, TestClient};
#[derive(Clone)]
struct Db { name: &'static str }
let app = Churust::server()
    .state(Db { name: "postgres" })
    .routing(|r| { r.get("/", |db: State<Db>| async move { db.name }); })
    .build();
assert_eq!(TestClient::new(app).get("/").send().await.text(), "postgres");
Source

pub fn advertise_http3(self, port: u16) -> Self

Advertise HTTP/3 on port to clients arriving over TCP.

Adds Alt-Svc: h3=":<port>"; ma=86400 to every response, which is how a browser learns that h3 exists at all: it reaches a new origin over TCP, reads this header, and uses QUIC for subsequent requests. Serving h3 without advertising it means almost nothing will ever use it.

Available with or without the http3 feature, because the process that terminates h3 is often a proxy in front rather than this server. Setting it when nothing is listening on that UDP port costs a client one failed QUIC attempt before it falls back, so only set it when something is.

use churust_core::{Call, Churust, TestClient};
let app = Churust::server()
    .advertise_http3(8443)
    .routing(|r| { r.get("/", |_c: Call| async { "ok" }); })
    .build();
let res = TestClient::new(app).get("/").send().await;
assert_eq!(res.header("alt-svc"), Some("h3=\":8443\"; ma=86400"));
Source

pub fn insert_state<T: Send + Sync + 'static>(&mut self, value: T)

Register application state through a mutable borrow.

The counterpart to state for callers that hold &mut AppBuilder rather than owning it, which is every Plugin: a plugin that wants to publish something for its own extractor to find has no other way to reach the state map. Same pairing as add_middleware and install_middleware.

Source

pub fn install<P: Plugin + 'static>(self, plugin: P) -> Self

Install a Plugin, letting it register middleware/state. Consumes the plugin value. Returns self for chaining.

Source

pub fn add_middleware_in(&mut self, phase: Phase, mw: Arc<dyn Middleware>)

Register a Middleware in a specific Phase. Plugins use this to place their middleware precisely; the builder sorts all middleware by phase (stably) at build time.

Source

pub fn add_middleware(&mut self, mw: Arc<dyn Middleware>)

Register a Middleware in the default Phase::Plugins phase — the common case for application middleware.

Source

pub fn routing(self, f: impl FnOnce(&mut RouteBuilder<'_>)) -> Self

Define routes via the RouteBuilder DSL. The closure receives a mutable builder on which to register handlers and nested scopes. Returns self for chaining.

use churust_core::{Churust, Call, TestClient};
let app = Churust::server()
    .routing(|r| {
        r.get("/", |_c: Call| async { "home" });
        r.post("/echo", |mut c: Call| async move { c.receive_text().await.unwrap_or_default() });
    })
    .build();
assert_eq!(TestClient::new(app).get("/").send().await.text(), "home");
Source

pub fn routes(&self) -> &[(Method, String)]

Every route registered so far, as (method, pattern).

Called between two routing blocks, this is the inventory an API description is generated from: describe what is already registered, then register the route that serves the description.

use churust_core::{Call, Churust};
use http::Method;

let builder = Churust::server().routing(|r| {
    r.get("/users/{id}", |_c: Call| async { "" });
});
assert_eq!(builder.routes(), &[(Method::GET, "/users/{id}".to_string())]);
Source

pub async fn start(self) -> Result<()>

Build the app and serve it until Ctrl-C — a shorthand for self.build().start().await. Binds a socket, so it does not return until shutdown.

use churust_core::{Churust, Call};
Churust::server()
    .routing(|r| { r.get("/", |_c: Call| async { "hi" }); })
    .start()
    .await
Source

pub fn on_error<F>(self, f: F) -> Self
where F: Fn(StatusCode, &Call) -> Option<Response> + Send + Sync + 'static,

Render error responses yourself — v1 design §5.3’s “StatusPages-lite”.

The closure runs for any response the pipeline produces with a 4xx or 5xx status, whether it came from a handler returning Err, a 404 for an unmatched path, or a 405. Return Some(response) to replace it, or None to keep the default rendering — so a hook can take over just the statuses it cares about.

It does not run for a request the server refused to admit: an oversized Content-Length, a message framed both by Transfer-Encoding and Content-Length, or a deadline that expired before the pipeline returned anything. Those are answered by the transport as 413, 400 and 408 in plain text, and deliberately so — running the pipeline would mean inventing a Call for a request that was never accepted, and every middleware with a side effect (a rate-limit counter, an audit entry, a session touch) would then record a request the server declined to dispatch. Security headers are applied to them, because those are added by the transport on the way out rather than by the pipeline.

It receives the status rather than the Error because by the time a response exists the error has already been rendered; this is the same shape as Ktor’s StatusPages, which Churust’s pipeline is modelled on.

use churust_core::{Call, Churust, Response, TestClient};
use http::StatusCode;
let app = Churust::server()
    .on_error(|status, call| {
        (status == StatusCode::NOT_FOUND)
            .then(|| Response::text(format!("no {} here", call.path())).with_status(status))
    })
    .routing(|r| { r.get("/", |_c: Call| async { "home" }); })
    .build();

let res = TestClient::new(app).get("/missing").send().await;
assert_eq!(res.text(), "no /missing here");
Source

pub fn install_middleware<M: Middleware>(self, mw: M) -> Self

Install a single Middleware app-wide, in the Plugins phase.

The chainable counterpart to add_middleware, which exists for plugins holding a &mut AppBuilder. For middleware that should apply to only part of the route tree, use RouteBuilder::intercept instead.

Source

pub fn security_headers(self, headers: SecurityHeaders) -> Self

Replace the default SecurityHeaders set.

use churust_core::{Churust, SecurityHeaders};
Churust::server()
    .security_headers(SecurityHeaders::new().frame_options(Some("SAMEORIGIN")));
Source

pub fn without_security_headers(self) -> Self

Send no security headers at all.

Reach for this only when something in front of the server already adds them; the defaults exist because most applications never get around to setting them by hand.

Source

pub fn bind(self, addr: impl Into<String>) -> Self

Bind an additional address.

Call it more than once to listen on several — IPv4 and IPv6, or a public port alongside an admin one. The configured host and port are always bound; this adds to them.

The addresses survive build, so they are honoured by App::start and App::start_with_shutdown just as much as by start. The two exceptions are the entry points that are handed a socket instead of choosing one — App::start_on takes an already-bound listener and start_unix serves a filesystem path — and neither binds the configured host:port either, so there is nothing for this to add to.

use churust_core::{Call, Churust};
Churust::server()
    .host("127.0.0.1")
    .port(8080)
    .bind("[::1]:8080")
    .routing(|r| { r.get("/", |_c: Call| async { "hi" }); })
    .start()
    .await
Source

pub async fn start_unix(self, path: impl AsRef<Path>) -> Result<()>

Serve on a Unix domain socket until Ctrl-C.

See engine::serve_unix. Unix only.

Source

pub fn build(self) -> App

Finish building into an immutable, cheaply-cloneable App.

Installed middleware is sorted by Phase (stably, preserving install order within a phase) and the configuration is frozen.

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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

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> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

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