Handler is a core concept in the Salvo framework, which can be simply understood as a request processing unit. It has two main purposes:
As an Endpoint: Objects implementing Handler can be placed in the routing system as endpoints to handle requests. When using the #[handler] macro, functions can directly serve as endpoints. The #[endpoint] macro not only allows functions to act as endpoints but also automatically generates OpenAPI documentation (this will be detailed in later documentation).
As Middleware: The same Handler can also function as middleware, processing requests before or after they reach the final endpoint.
Salvo's request processing flow can be visualized as a "pipeline": requests first pass through a series of middleware (vertical processing) before reaching the matched endpoint (horizontal processing). Both middleware and endpoints are implementations of Handler, ensuring system-wide consistency and flexibility.
The essence of the onion model lies in the placement of ctrl.call_next(), enabling bidirectional processing of requests and responses. This allows each middleware to participate in the complete request-response cycle.
A Handler is an object responsible for processing Request objects. The Handler itself is a Trait containing an asynchronous handle method:
The handle function's default signature includes four parameters: &mut Request, &mut Depot, &mut Response, and &mut FlowCtrl. Depot is temporary storage for request-related data.
Depending on usage, a Handler can serve as middleware (hoop), processing requests before or after they reach the final Handler—e.g., for authentication, data compression, etc.
Middleware is added via the hoop function of Router. Added middleware affects the current Router and all its descendant Routers.
A Handler can also serve as a goal—participating in routing matching and final execution.
Handler as Middleware (hoop)When a Handler acts as middleware, it can be added to three types of middleware-supporting objects:
Service: All requests pass through middleware defined in Service.Router: Only successfully matched requests pass through middleware defined in Service and all middleware collected along the matched path.Catcher: When an error occurs and no custom error message is written, requests pass through middleware in Catcher.Handler: A Handler itself supports adding middleware wrappers for pre- or post-processing logic.#[handler] MacroThe #[handler] macro significantly simplifies code and enhances flexibility.
It can be applied to a function to implement Handler:
This is equivalent to:
As shown, using #[handler] makes the code much simpler:
#[async_trait] is no longer needed.Writer or Scribe traits can be directly returned. Here, &'static str implements Scribe, so it can be returned directly.The #[handler] macro can also be applied to a struct's impl block to implement Handler. In this case, the handle function in the impl block is recognized as the concrete implementation of Handler's handle:
In Salvo, a Handler can return Result, provided the Ok and Err types both implement the Writer trait.
Given the widespread use of anyhow, when the anyhow feature is enabled, anyhow::Error implements the Writer trait and is mapped to InternalServerError.
For custom error types, you can render different error pages as needed: