actix_web

Module middleware

source
Expand description

A collection of common middleware.

§What Is Middleware?

Actix Web’s middleware system allows us to add additional behavior to request/response processing. Middleware can hook into incoming request and outgoing response processes, enabling us to modify requests and responses as well as halt request processing to return a response early.

Typically, middleware is involved in the following actions:

Middleware is registered for each App, Scope, or Resource and executed in opposite order as registration.

§Simple Middleware

In many cases, you can model your middleware as an async function via the from_fn() helper that provides a natural interface for implementing your desired behaviors.

use actix_web::middleware::{self, Next};

async fn my_mw(
    req: ServiceRequest,
    next: Next<impl MessageBody>,
) -> Result<ServiceResponse<impl MessageBody>, Error> {
    // pre-processing

    // invoke the wrapped middleware or service
    let res = next.call(req).await?;

    // post-processing

    Ok(res)
}

App::new()
    .wrap(middleware::from_fn(my_mw));

§Complex Middleware

In the more general ase, a middleware is a pair of types that implements the Service trait and Transform trait, respectively. The new_transform and call methods must return a Future, though it can often be an immediately-ready one.

All the built-in middleware use this pattern with pairs of builder (Transform) + implementation (Service) types.

§Ordering

#[get("/")]
async fn service(a: ExtractorA, b: ExtractorB) -> impl Responder { "Hello, World!" }

let app = App::new()
    .wrap(MiddlewareA::default())
    .wrap(MiddlewareB::default())
    .wrap(MiddlewareC::default())
    .service(service);
                  Request
                     ⭣
╭────────────────────┼────╮
│ MiddlewareC        │    │
│ ╭──────────────────┼───╮│
│ │ MiddlewareB      │   ││
│ │ ╭────────────────┼──╮││
│ │ │ MiddlewareA    │  │││
│ │ │ ╭──────────────┼─╮│││
│ │ │ │ ExtractorA   │ ││││
│ │ │ ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┼┈┤│││
│ │ │ │ ExtractorB   │ ││││
│ │ │ ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┼┈┤│││
│ │ │ │ service      │ ││││
│ │ │ ╰──────────────┼─╯│││
│ │ ╰────────────────┼──╯││
│ ╰──────────────────┼───╯│
╰────────────────────┼────╯
                     ⭣
                  Response

The request first gets processed by the middleware specified last - MiddlewareC. It passes the request (possibly a modified one) to the next middleware - MiddlewareB - or directly responds to the request (e.g. when the request was invalid or an error occurred). MiddlewareB processes the request as well and passes it to MiddlewareA, which then passes it to the Service. In the Service, the extractors will run first. They don’t pass the request on, but only view it (see FromRequest). After the Service responds to the request, the response is passed back through MiddlewareA, MiddlewareB, and MiddlewareC.

As you register middleware using wrap and wrap_fn in the App builder, imagine wrapping layers around an inner App. The first middleware layer exposed to a Request is the outermost layer (i.e., the last registered in the builder chain, in the example above: MiddlewareC). Consequently, the first middleware registered in the builder chain is the last to start executing during request processing (MiddlewareA). Ordering is less obvious when wrapped services also have middleware applied. In this case, middleware are run in reverse order for App and then in reverse order for the wrapped service.

§Middleware Traits

§Transform<S, Req>

The Transform trait is the builder for the actual Services that handle the requests. All the middleware you pass to the wrap methods implement this trait. During construction, each thread assembles a chain of Services by calling new_transform and passing the next Service (S) in the chain. The created Service handles requests of type Req.

In the example from the ordering section, the chain would be:

MiddlewareCService {
    next: MiddlewareBService {
        next: MiddlewareAService { ... }
    }
}

§Service<Req>

A Service S represents an asynchronous operation that turns a request of type Req into a response of type S::Response or an error of type S::Error. You can think of the service of being roughly:

async fn(&self, req: Req) -> Result<S::Response, S::Error>

In most cases the Service implementation will, at some point, call the wrapped Service in its call implementation.

Note that the Services created by new_transform don’t need to be Send or Sync.

§Example

use std::{future::{ready, Ready, Future}, pin::Pin};

use actix_web::{
    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
    web, Error,
};

pub struct SayHi;

// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S, ServiceRequest> for SayHi
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = SayHiMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(SayHiMiddleware { service }))
    }
}

pub struct SayHiMiddleware<S> {
    /// The next service to call
    service: S,
}

// This future doesn't have the requirement of being `Send`.
// See: futures_util::future::LocalBoxFuture
type LocalBoxFuture<T> = Pin<Box<dyn Future<Output = T> + 'static>>;

// `S`: type of the wrapped service
// `B`: type of the body - try to be generic over the body where possible
impl<S, B> Service<ServiceRequest> for SayHiMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<Result<Self::Response, Self::Error>>;

    // This service is ready when its next service is ready
    forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        println!("Hi from start. You requested: {}", req.path());

        // A more complex middleware, could return an error or an early response here.

        let fut = self.service.call(req);

        Box::pin(async move {
            let res = fut.await?;

            println!("Hi from response");
            Ok(res)
        })
    }
}

let app = App::new()
    .wrap(SayHi)
    .route("/", web::get().to(|| async { "Hello, middleware!" }));

Structs§

  • Middleware for enabling any middleware to be used in Resource::wrap, and Condition.
  • Middleware for compressing response payloads.
  • Middleware for conditionally enabling other middleware.
  • Middleware for setting default response headers.
  • Middleware for registering custom status code based error handlers.
  • A no-op middleware that passes through request and response untouched.
  • Middleware for logging request and response summaries to the terminal.
  • Wraps the “next” service in the middleware chain.
  • Middleware for normalizing a request’s path so that routes can be matched more flexibly.

Enums§

Functions§

  • Wraps an async function to be used as a middleware.