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
impl AppBuilder
Sourcepub fn host(self, host: impl Into<String>) -> Self
pub fn host(self, host: impl Into<String>) -> Self
Set the bind host (default "127.0.0.1"). Returns self for chaining.
Sourcepub fn port(self, port: u16) -> Self
pub fn port(self, port: u16) -> Self
Set the bind port (default 8080). Returns self for chaining.
Sourcepub fn max_body_bytes(self, n: usize) -> Self
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.
Sourcepub fn with_config(self, cfg: Config) -> Self
pub fn with_config(self, cfg: Config) -> Self
Sourcepub fn request_timeout_ms(self, ms: u64) -> Self
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.
Sourcepub fn header_read_timeout_ms(self, ms: u64) -> Self
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.
Sourcepub fn max_headers(self, n: usize) -> Self
pub fn max_headers(self, n: usize) -> Self
Set the maximum number of request headers accepted (default 100).
Sourcepub fn keep_alive_ms(self, ms: u64) -> Self
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.
Sourcepub fn shutdown_timeout_ms(self, ms: u64) -> Self
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.
Sourcepub fn path_policy(self, policy: PathPolicy) -> Self
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.
Sourcepub fn h2_max_header_list_size(self, n: u32) -> Self
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.
Sourcepub fn h2_max_concurrent_streams(self, n: u32) -> Self
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.
Sourcepub fn ws_idle_timeout_ms(self, ms: u64) -> Self
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.
Sourcepub fn max_connections(self, n: usize) -> Self
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.
Sourcepub fn max_tls_handshakes(self, n: usize) -> Self
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.
Sourcepub fn tls_handshake_timeout_ms(self, ms: u64) -> Self
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.
Sourcepub fn max_path_segments(self, n: usize) -> Self
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.
Sourcepub fn tls(
self,
cert_path: impl Into<String>,
key_path: impl Into<String>,
) -> Self
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.
Sourcepub fn state<T: Send + Sync + 'static>(self, value: T) -> Self
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");Sourcepub fn advertise_http3(self, port: u16) -> Self
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"));Sourcepub fn insert_state<T: Send + Sync + 'static>(&mut self, value: T)
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.
Sourcepub fn install<P: Plugin + 'static>(self, plugin: P) -> Self
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.
Sourcepub fn add_middleware_in(&mut self, phase: Phase, mw: Arc<dyn Middleware>)
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.
Sourcepub fn add_middleware(&mut self, mw: Arc<dyn Middleware>)
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.
Sourcepub fn routing(self, f: impl FnOnce(&mut RouteBuilder<'_>)) -> Self
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");Sourcepub fn routes(&self) -> &[(Method, String)]
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())]);Sourcepub async fn start(self) -> Result<()>
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()
.awaitSourcepub fn on_error<F>(self, f: F) -> Self
pub fn on_error<F>(self, f: F) -> Self
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");Sourcepub fn install_middleware<M: Middleware>(self, mw: M) -> Self
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.
Sourcepub fn security_headers(self, headers: SecurityHeaders) -> Self
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")));Sourcepub fn without_security_headers(self) -> Self
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.
Sourcepub fn bind(self, addr: impl Into<String>) -> Self
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()
.awaitSourcepub async fn start_unix(self, path: impl AsRef<Path>) -> Result<()>
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.