Module actix_web::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. In general, 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.

§Ordering

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

let app = App::new()
    .wrap(MiddlewareA)
    .wrap(MiddlewareB)
    .wrap(MiddlewareC)
    .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 (modified 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!" }));

§Simpler Middleware

In many cases, you can actually use an async function via a helper that will provide a more natural flow for your behavior.

The experimental actix_web_lab crate provides a from_fn utility which allows an async fn to be wrapped and used in the same way as other middleware. See the from_fn docs for more info and examples of it’s use.

While from_fn is experimental currently, it’s likely this helper will graduate to Actix Web in some form, so feedback is appreciated.

Structs§

  • Middleware for enabling any middleware to be used in Resource::wrap, and Condition.
  • Compress__compress
    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.
  • Middleware for logging request and response summaries to the terminal.
  • Middleware for normalizing a request’s path so that routes can be matched more flexibly.

Enums§