Skip to content

Middleware

The Middleware trait

Middleware is defined in src/middleware/mod.rs:

pub trait Middleware: Send + Sync {
fn handle(
&self,
request: &Request,
connection: &ConnectionInfo,
next: &dyn Application,
) -> Result<Response, String>;
}

Call next.execute(request, connection) to pass the request to the next layer (or the inner application). Return a Response directly to short-circuit the chain.

Wrapping an application

.wrap(layer) attaches a middleware layer to any Application. Layers run in registration order — the first .wrap() call is the outermost layer (runs first on the way in, last on the way out):

use rust_web_server::app::App;
use rust_web_server::middleware::{RateLimitLayer, WithMiddleware};
use rust_web_server::core::New;
let app = App::new()
.wrap(RateLimitLayer); // outermost: checked before any route

Chain multiple layers:

use rust_web_server::middleware::MetricsLayer;
let app = App::new()
.wrap(RateLimitLayer) // checked first
.wrap(MetricsLayer); // checked second

App::with_state(S) and Router-based apps all expose the same .wrap() method.

Built-in middleware layers

LayerModuleDescription
RateLimitLayermiddlewareEnforces the process-wide sliding-window rate limit; returns 429 when the budget for the client IP is exceeded. Configured via RWS_CONFIG_RATE_LIMIT_MAX_REQUESTS / RWS_CONFIG_RATE_LIMIT_WINDOW_SECS.
MetricsLayermetricsRecords per-route rws_route_requests_total counters and rws_route_duration_seconds histograms in Prometheus format.
CacheLayercacheIn-process response cache; caches GET responses by URI.
OtelLayerotelEmits OpenTelemetry spans and propagates W3C traceparent context.
RewriteLayerrewriteApplies configurable request/response rewrite rules (header set/remove, URI prefix manipulation, response body find-and-replace).
ReverseProxyproxyHTTP/1.1 reverse proxy; forwards the request to a backend and returns its response.
H2ReverseProxyproxyHTTP/2 upstream proxy (requires http2 feature).
GrpcProxyproxyWraps H2ReverseProxy; only proxies requests with Content-Type: application/grpc*.
BasicAuthLayerauthHTTP Basic authentication; calls a user-supplied verifier closure.
JwtLayerauthJWT Bearer token verification; rejects requests without a valid token.
IpFilterip_filterAllow-list or deny-list based on client IP address or CIDR range.
BlocklistLayerblocklistRuntime-updatable IP blocklist; unlike IpFilter, the list can be modified without restart.
CanaryLayercanaryRoutes a configured percentage of traffic to a canary backend.
RetryLayercircuit_breakerRetries failing requests up to a configured limit with backoff.
CsrfLayercsrfCSRF token validation for state-mutating requests.
LogLayerrequest_logStructured per-request access logging (combined or JSON format).
MaintenanceLayermaintenanceReturns 503 Service Unavailable when maintenance mode is active.

Quick usage examples

use rust_web_server::app::App;
use rust_web_server::core::New;
use rust_web_server::middleware::{RateLimitLayer, MetricsLayer};
use rust_web_server::ip_filter::IpFilter;
use rust_web_server::auth::JwtLayer;
use rust_web_server::request_log::LogLayer;
let app = App::new()
.wrap(LogLayer)
.wrap(IpFilter::deny(["1.2.3.4", "10.0.0.0/8"]))
.wrap(JwtLayer)
.wrap(RateLimitLayer)
.wrap(MetricsLayer);

Writing a custom middleware layer

Implement the Middleware trait on a unit struct (or a struct carrying configuration):

use rust_web_server::middleware::Middleware;
use rust_web_server::application::Application;
use rust_web_server::request::Request;
use rust_web_server::response::Response;
use rust_web_server::server::ConnectionInfo;
/// Adds an X-Request-Id header to every response.
pub struct RequestIdLayer;
impl Middleware for RequestIdLayer {
fn handle(
&self,
request: &Request,
connection: &ConnectionInfo,
next: &dyn Application,
) -> Result<Response, String> {
// Call into the rest of the chain / inner application
let mut response = next.execute(request, connection)?;
// Mutate the response on the way out
let id = format!("{}-{}", connection.server.ip, connection.server.port);
response.headers.push(rust_web_server::header::Header {
name: "X-Request-Id".to_string(),
value: id,
});
Ok(response)
}
}

Register it like any built-in layer:

use rust_web_server::app::App;
use rust_web_server::core::New;
let app = App::new().wrap(RequestIdLayer);

Short-circuit example

Return a response without calling next to block the request:

use rust_web_server::middleware::Middleware;
use rust_web_server::application::Application;
use rust_web_server::error::{AppError, IntoResponse};
use rust_web_server::request::{METHOD, Request};
use rust_web_server::response::Response;
use rust_web_server::server::ConnectionInfo;
/// Rejects all non-GET/HEAD requests to /public/*.
pub struct ReadOnlyPublicLayer;
impl Middleware for ReadOnlyPublicLayer {
fn handle(
&self,
request: &Request,
connection: &ConnectionInfo,
next: &dyn Application,
) -> Result<Response, String> {
let is_write = request.method != METHOD.get && request.method != METHOD.head;
let is_public = request.request_uri.starts_with("/public/");
if is_write && is_public {
return Ok(AppError::Forbidden.into_response());
}
next.execute(request, connection)
}
}