pub struct RouteBuilder<S> { /* private fields */ }Expand description
Builder for configuring routes and settings before creating an App.
RouteBuilder uses a fluent API to register routes, configure CORS, and adjust
server settings. Call .seal() to produce the final App<S>.
§Example
use mini_serve::{RouteBuilder, handler, body};
use hyper::Response;
use hyper::body::Bytes;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = RouteBuilder::stateless()
.get("/health", handler(|_, _| async {
Ok(Response::new(body(Bytes::from("OK"))))
}))
.seal();
app.bind("127.0.0.1:8080".parse()?).await?;
Ok(())
}Implementations§
Source§impl<S: Send + Sync + 'static> RouteBuilder<S>
impl<S: Send + Sync + 'static> RouteBuilder<S>
Sourcepub fn wrap(self, middleware: Middleware<S>) -> Self
pub fn wrap(self, middleware: Middleware<S>) -> Self
Register a middleware. Applies to every route registered after this
call — middlewares wrap in registration order, so the first .wrap()
call becomes the outermost layer and runs first.
Sourcepub fn with_fallback(self, handler: Handler<S>) -> Self
pub fn with_fallback(self, handler: Handler<S>) -> Self
Handle requests that matched no route at all.
Runs only when the path matches nothing for any method. A path registered for a
different method still answers 405 with Allow, so this does not change method
semantics — it fills the branch that would otherwise be a flat 404.
Wrapped by the middlewares registered before it, exactly as a route is. That is
not an implementation detail to preserve casually: middleware is applied per route
at registration, so a fallback attached any other way would run outside the
chain, and an authorization middleware guarding a path prefix would not protect
whatever the fallback serves for that prefix. See fallback-is-wrapped in
verify-guarantees.sh.
The response goes through the same exit as every other branch, so it carries
nosniff, CORS headers and HEAD body-stripping without doing anything itself.
Sourcepub fn with_max_body_size(self, max: usize) -> Self
pub fn with_max_body_size(self, max: usize) -> Self
Set the maximum request body size in bytes (default: 2 MiB).
Sourcepub fn with_header_read_timeout(self, d: Duration) -> Self
pub fn with_header_read_timeout(self, d: Duration) -> Self
Set the header read timeout (default: 30 seconds).
Sourcepub fn with_response_header(
self,
name: &str,
value: &str,
) -> Result<Self, ServeError>
pub fn with_response_header( self, name: &str, value: &str, ) -> Result<Self, ServeError>
Log one line per request to stderr, plus handler panics and the internal detail behind 5xx responses.
Off by default: a library writing to its host’s stderr uninvited is a surprise.
See RouteBuilder::with_request_logging_to to choose the destination.
Send name: value on every response that does not already carry that header.
For the policy headers an API wants applied uniformly — Strict-Transport-Security,
Content-Security-Policy, Referrer-Policy.
Apply-if-absent, deliberately: a handler that sets the header itself wins.
This is the opposite of mini-static’s rule, and for the opposite reason — that
crate computes every header itself, so a fixed value fighting a computed one is a
bug, whereas handlers here are arbitrary user code that may legitimately vary a
policy per route.
§Errors
ServeError if name or value is not a valid HTTP header, or if name is
one the connection layer owns (Content-Length, Connection,
Transfer-Encoding) — those describe framing this crate does not choose, so a
fixed value could only contradict it.
pub fn with_request_logging(self) -> Self
Sourcepub fn with_request_logging_to(self, writer: Box<dyn Write + Send>) -> Self
pub fn with_request_logging_to(self, writer: Box<dyn Write + Send>) -> Self
Log one line per request to writer, plus handler panics and 5xx internals.
Each served request writes GET /path 200 0.421ms — method, path exactly as
received, status, and handling time. Two failures that are otherwise invisible
also come here:
- Handler panics. The task’s result was previously discarded, so a panicking handler dropped the client’s connection and left no trace anywhere.
- 5xx internal detail. The client body is sanitized to
{"message":"internal server error"}deliberately; without a sink the real message was discarded with it, leaving an operator nothing to debug from.
The path is logged exactly as received, not decoded: it is attacker-controlled input, and whoever reads the log deserves the bytes that actually arrived. Writes are serialized across connections; write failures are ignored rather than allowed to fail a request.
Sourcepub fn with_connect_timeout(self, d: Duration) -> Self
pub fn with_connect_timeout(self, d: Duration) -> Self
How long a transport has to turn an accepted connection into a usable one (default: 10 seconds).
Bounds the connect step of App::run_with_transport, so a transport that
negotiates forever cannot hold a connection slot forever. It has no effect on the
identity transport App::run uses, which cannot block — the guarantee exists for
transports that do work, which is the only kind worth plugging in.
Replaces the former with_tls_handshake_timeout: the bound belongs to the server’s
connection lifecycle rather than to any one transport.
Sourcepub fn with_max_header_bytes(self, bytes: usize) -> Self
pub fn with_max_header_bytes(self, bytes: usize) -> Self
Set the maximum concurrent connections (default: 1024). Cap the request header block (default 64 KiB).
hyper refuses a block that will not fit and closes the connection, so this bounds what one connection can make the process hold before any handler runs.
Sourcepub fn with_upgrades(self) -> Self
pub fn with_upgrades(self) -> Self
Allow handlers to take over a connection with OnUpgrade.
Off by default: an application with no upgrade route should not pay for the
capability, and enabling it changes how every connection on this server is served.
Without it, a 101 response is written and the callback never runs — which is
reported to the log sink if one is configured, since the client would otherwise see
a dead connection and the author would see nothing.
The server takes the request’s upgrade future before routing, so a handler that
calls hyper::upgrade::on itself receives nothing. Use OnUpgrade instead; it
exists so the upgraded stream is serviced inside the connection’s own task, keeping
it inside the connection ceiling and the shutdown drain.
pub fn with_max_connections(self, max: usize) -> Self
pub fn with_error_handler( self, f: impl Fn(StatusCode, &str) -> Response<ResponseBody> + Send + Sync + 'static, ) -> Self
pub fn with_cors(self, config: CorsConfig) -> Self
pub fn get(self, path: &str, handler: Handler<S>) -> Self
pub fn post(self, path: &str, handler: Handler<S>) -> Self
pub fn put(self, path: &str, handler: Handler<S>) -> Self
pub fn delete(self, path: &str, handler: Handler<S>) -> Self
Sourcepub fn patch(self, path: &str, handler: Handler<S>) -> Self
pub fn patch(self, path: &str, handler: Handler<S>) -> Self
Register a PATCH handler.
Dispatch and the Allow header are generic over Method, so this needs nothing
from the router that its siblings do not — it was simply missing.