Pre-processing
Pre-processing middlewares execute ahead of the request handler.
They can be used to enforce pre-conditions and return an early response if they are not met,
skipping the rest of the request processing pipeline.
E.g., rejecting unauthenticated requests or enforcing rate limits.
use pavex::http::{header::LOCATION, HeaderValue};
use pavex::middleware::Processing;
use pavex::request::RequestHead;
use pavex::response::Response;
/// If the request path ends with a `/`,
/// redirect to the same path without the trailing `/`.
pub fn redirect_to_normalized(request_head: &RequestHead) -> Processing {
let Some(normalized_path) = request_head.target.path().strip_suffix('/') else {
// No need to redirect, we continue processing the request.
return Processing::Continue;
};
let location = HeaderValue::from_str(normalized_path).unwrap();
let redirect = Response::temporary_redirect().insert_header(LOCATION, location);
// Short-circuit the request processing pipeline and return a redirect response
Processing::EarlyReturn(redirect)
}
Registration
You register a pre-processing middleware against a blueprint via the pre_process
method.
use pavex::blueprint::router::GET;
use pavex::blueprint::Blueprint;
use pavex::f;
pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.pre_process(f!(super::redirect_to_normalized));
bp.route(GET, "/", f!(super::handler));
bp
}
You must provide an unambiguous path to the middleware, wrapped in the f!
macro.
The middleware will be invoked for all request handlers registered after it, as long as they were registered against the same Blueprint
or one of its nested children.
Check out the scoping section for more details.
Registration syntax
You can use free functions, static methods, non-static methods, and trait methods as middlewares. Check out the dependency injection cookbook for more details on the syntax for each case.
Processing
Pre-processing middlewares must return Processing
.
use pavex::http::{header::LOCATION, HeaderValue};
use pavex::middleware::Processing;
use pavex::request::RequestHead;
use pavex::response::Response;
/// If the request path ends with a `/`,
/// redirect to the same path without the trailing `/`.
pub fn redirect_to_normalized(request_head: &RequestHead) -> Processing {
let Some(normalized_path) = request_head.target.path().strip_suffix('/') else {
// No need to redirect, we continue processing the request.
return Processing::Continue;
};
let location = HeaderValue::from_str(normalized_path).unwrap();
let redirect = Response::temporary_redirect().insert_header(LOCATION, location);
// Short-circuit the request processing pipeline and return a redirect response
Processing::EarlyReturn(redirect)
}
Processing
is an enum with two variants:
Processing::Continue
: the middleware has finished its work and the request processing pipeline should continue as usual.Processing::EarlyReturn(T)
: the remaining pre-processing and wrapping middlewares won't be invoked, nor will the request handler.T
will be converted into a response, fed to the post-processing middlewares (if there are any) and then sent back to the client.
Check out the execution order guide for more details on how the different types of middlewares interact with each other.
T
, the type inside Processing::EarlyReturn
, must implement the
IntoResponse
trait.
If T
is not specified, it defaults to Response
.
Middlewares can fail
Your pre-processing middlewares can be fallible, i.e. they can return a Result
.
use pavex::middleware::Processing;
use pavex::request::RequestHead;
use super::AuthError;
pub async fn reject_anonymous(request: &RequestHead) -> Result<Processing, AuthError>
{
if request.headers.get("Authorization").is_none() {
return Err(AuthError);
}
Ok(Processing::Continue)
}
If they do, you must specify an error handler when registering them:
use pavex::blueprint::router::GET;
use pavex::blueprint::Blueprint;
use pavex::f;
pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.pre_process(f!(super::reject_anonymous))
.error_handler(f!(super::auth_error_handler));
// [...]
bp
}
Check out the error handling guide for more details.
Result
or Processing::EarlyReturn
?
The rest of the request processing pipeline will be skipped if your pre-processing middleware returns an error.
Why does
Processing::EarlyReturn
exist then? Can't I just return an error when I want to skip the rest of the pipeline?
You can, but an error has a different semantic meaning.
An error is a problem that occurred during the processing of the request.
Pavex will invoke error observers, if they were registered, and your application will
probably emit error-level logs, increment error counters, etc.
There are scenarios where you want to return an early response, but it's not an error.
E.g., you might want to redirect all requests with a trailing slash to the same URL without the trailing slash.
An early return is a normal response, not an error.
Choose the short-circuiting mechanism that best fits the semantics of your use case.
Dependency injection
Pre-processing middlewares can take advantage of dependency injection.
You must specify the dependencies of your middleware as input parameters in its function signature.
Those inputs are going to be built and injected by the framework,
according to the constructors you have registered.
Pre-processing middlewares, like request handlers and post-processing middlewares,
can mutate request-scoped types.
Ask for a &mut
reference to the type you want to mutate as an input parameter, the framework will take care of the rest.
Check out the dependency injection guide for more details
on how the process works.
Check out the request data guide for an overview of the data you can extract from the request
using Pavex's first-party extractors.