Pre-processing
Pre-processing middlewares execute ahead of the route handler.
They can 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.
Defining pre-processing middlewares
Use the #[pre_process] attribute to define a new pre-processing middleware:
use pavex::Response;
use pavex::http::{HeaderValue, header::LOCATION};
use pavex::middleware::Processing;
use pavex::pre_process;
use pavex::request::RequestHead;
/// If the request path ends with a `/`,
/// redirect to the same path without the trailing `/`.
#[pre_process]
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)
}
Signature
The return type of a pre-processing middleware must be one of the following:
Processing, if the middleware is infallible, orResult<Processing, E>, whereEis an error type, if the middleware can fail.
Other than that, you have a lot of freedom in how you define your pre-processing middlewares:
- They can be free functions or methods.
- They can be synchronous or asynchronous.
- They can take zero or more parameters, leaning on Pavex's dependency injection system.
Registration
Invoke Blueprint::pre_process to register a pre-processing middleware:
use crate::redirect::REDIRECT_TO_NORMALIZED;
use pavex::{Blueprint, blueprint::from};
pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.pre_process(REDIRECT_TO_NORMALIZED); // (1)!
// [...]
}
REDIRECT_TO_NORMALIZEDis a strongly-typed constant generated by the#[pre_process]attribute on theredirect_to_normalizedfunction.
Check out the documentation on component ids for more details.
The middleware will be invoked for all routes registered after it. Check out the scoping section for more details.
Processing
Pre-processing middlewares must return Processing, either directly or as the Ok variant of a Result.
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 route handler.Twill 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
Pre-processing middlewares can return a Result.
use crate::errors::AuthError;
use pavex::middleware::Processing;
use pavex::pre_process;
use pavex::request::RequestHead;
#[pre_process]
pub fn reject_anonymous(request: &RequestHead) -> Result<Processing, AuthError> {
if request.headers.get("Authorization").is_none() {
return Err(AuthError);
}
Ok(Processing::Continue)
}
Check out the error handling guide for more details on how to handle the error case.
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::EarlyReturnexist 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 an expected scenario, 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.