Request handlers
A request handler is invoked when a request matches on the associated method guard and path pattern. The request handler is in charge of building the response that will be sent back to the client.
Registration
You must provide an unambiguous path to the request handler, wrapped in the [f!
][f] macro.
use pavex::blueprint::{router::GET, Blueprint};
use pavex::f;
pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.route(GET, "/greet", f!(crate::routes::greet));
bp
}
The path must be wrapped in the f!
macro.
Registration syntax
You can use free functions, static methods, non-static methods, and trait methods as request handlers. Check out the dependency injection cookbook for more details on the syntax for each case.
IntoResponse
A request handler must return a type that can be converted into a Response
via the
IntoResponse
trait.
You only have three first-party types to choose from:
This is an explicit design choice. Pavex implements the IntoResponse
trait exclusively for types
that don't require the framework to assume or infer a suitable status code.
If you want to return a custom type from your request handler, you must implement IntoResponse
for it.
Dependency injection
Request handlers are expected to take into account the incoming request when building the response. How does that work in Pavex? How do you extract data from the request?
You must take advantage of dependency injection.
You must specify the dependencies of your request handler 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.
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.
Request handlers can fail
Your request handlers can be fallible, i.e. they can return a Result
.
If they do, you must specify an error handler when registering the route:
use pavex::blueprint::{router::GET, Blueprint};
use pavex::f;
pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.route(GET, "/greet", f!(crate::routes::greet))
.error_handler(f!(crate::error_handler::greet_error_handler));
bp
}
Check out the error handling guide for more details.
Sync or async?
Request handlers can either be synchronous or asynchronous:
use pavex::http::StatusCode;
pub async fn async_greet() -> StatusCode {
StatusCode::OK
}
pub fn sync_greet() -> StatusCode {
StatusCode::OK
}
There is no difference when registering the route with the Blueprint
:
use pavex::blueprint::{router::GET, Blueprint};
use pavex::f;
pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.route(GET, "/async_greet", f!(crate::routes::async_greet));
bp.route(GET, "/sync_greet", f!(crate::routes::sync_greet));
bp
}
Be careful with synchronous handlers: they block the thread they're running on until they return.
That's not a concern if you are performing an operation that's guaranteed to be fast
(e.g. building a response from static data).
It becomes a problem if you're doing work that's potentially slow.
There are two types of work that can be slow:
- I/O operations (e.g. reading from a file, querying a database, etc.)
- CPU-intensive operations (e.g. computing a password hash, parsing a large file, etc.)
As a rule of thumb:
I/O | CPU-intensive | Handler type | Notes |
---|---|---|---|
Yes | No | Async | Use async libraries for the I/O portion. If the I/O interface is synchronous, use tokio::task::spawn_blocking . |
No | Yes | Async | Use tokio::task::spawn_blocking for the CPU-intensive portion. |
Yes | Yes | Async | See above. |
No | No | Sync | You can also make it asynchronous, it doesn't matter. |
If you want to learn more about what "blocking" means in async Rust, check out Alice Rhyl's excellent article.