Skip to content

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.

src/core/mw.rs
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.

src/core/blueprint.rs
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.

src/core/mw.rs
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.

src/fallible/mw.rs
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:

src/fallible/blueprint.rs
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.