Handler
Quick Overview
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: An object implementing
Handlercan be placed into the routing system as the final endpoint to process requests. When using the#[handler]macro, a function can be directly used as an endpoint; while using the#[endpoint]macro not only allows it to serve as an endpoint but also automatically generates OpenAPI documentation (this will be detailed in subsequent documentation). -
As Middleware: The same
Handlercan also be used as middleware to process requests before or after they reach the final endpoint.
Salvo's request processing flow can be viewed as a "pipeline": a request first passes through a series of middleware (vertical processing) and then reaches the matching endpoint (horizontal processing). Both middleware and endpoints are implementations of Handler, which ensures consistency and flexibility throughout the system.
Handler Flowchart in Salvo
Middleware and the Onion Model
The essence of the onion model is that by placing ctrl.call_next() before and after specific logic, it implements a bidirectional processing flow for requests and responses, allowing each middleware to participate in the complete request-response cycle.
Complete Middleware Example Structure
What is a Handler
A Handler is the concrete object responsible for processing Request objects. Handler itself is a Trait containing an asynchronous handle method:
The default signature of the handle function includes four parameters, in order: &mut Request, &mut Depot, &mut Response, &mut FlowCtrl. Depot is temporary storage that can hold data related to the current request.
Depending on how it's used, it can serve as middleware (hoop), which can perform processing before or after the request reaches the formal request-processing Handler, such as: login verification, data compression, etc.
Middleware is added via the hoop function of a Router. The added middleware affects the current Router and all its descendant Routers.
A Handler can also be used as a Handler that participates in route matching and is ultimately executed, known as a goal.
Handler as Middleware (hoop)
When a Handler acts as middleware, it can be added to the following three types of objects that support middleware:
Service: Any request will pass through the middleware in theService.Router: Only when route matching succeeds will the request pass through the middleware defined in theServiceand all middleware collected along the matching path.Catcher: When an error occurs and no custom error information has been written, the request will pass through the middleware in theCatcher.Handler: TheHandleritself supports adding middleware wrappers to execute some pre- or post-logic.
Using the #[handler] Macro
The #[handler] macro can greatly simplify code writing and improve code flexibility.
It can be applied to a function to make it implement Handler:
This is equivalent to:
As you can see, with #[handler], the code becomes much simpler:
- No need to manually add
#[async_trait]. - Unnecessary parameters in the function are omitted, and the required parameters can be arranged in any order.
- For objects implementing the
WriterorScribeabstractions, they can be directly returned as the function's return value. Here,&'static strimplementsScribe, so it can be directly returned.
#[handler] can not only be applied to functions but also to the impl block of a struct to make the struct implement Handler. In this case, the handle function within the impl block is recognized as the concrete implementation of the handle method in Handler:
Handling Errors
In Salvo, a Handler can return a Result, provided that both the Ok and Err types within the Result implement the Writer trait.
Considering the widespread use of anyhow, when the anyhow feature is enabled, anyhow::Error will implement the Writer trait. anyhow::Error will be mapped to InternalServerError.
For custom error types, you can output different error pages as needed.