actix-web extractors 支持提取任意长度的参数
- tags: Rust
背景⌗
今天看了一下 actix-web 发现该框架支持基于参数的 Extractor,可以非常方便地解析参数(包括 URI、Query、JSON 和 FormData)。
先来看一个在项目 README.md
中的例子:
use actix_web::{get, web, App, HttpServer, Responder};
#[get("/{id}/{name}/index.html")]
async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", name, id)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index))
.bind("127.0.0.1:8080")?
.run()
.await
}
初看之下觉得很神奇,但细想通过宏实现应该不是特别困难,然后发现其官网还有不是基于宏的运行时调用:
use actix_web::{web, App, HttpRequest, HttpServer, Responder};
async fn greet(req: HttpRequest) -> impl Responder {
let name = req.match_info().get("name").unwrap_or("World");
format!("Hello {}!", &name)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(greet))
.route("/{name}", web::get().to(greet))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
看下来方法 to
的签名,实现一个参数的提取也不困难:
pub fn to<F, T, R>(mut self, handler: F) -> Self
where
F: Handler<T, R>,
T: FromRequest + 'static,
R: Future + 'static,
R::Output: Responder + 'static,
<R::Output as Responder>::Body: MessageBody,
<<R::Output as Responder>::Body as MessageBody>::Error: Into<BoxError>,
{
self.service = handler_service(handler);
self
}
但是尝试之后发现 to
同样可以支持多个参数,基于宏实现对于多个参数的支持相对比较简单,但是对于不通过宏实现对多个参数的解析就很神奇了。
原理探究⌗
经过深入了解之后发现底层原理大体如下
trait Handler<T, R>: 'static {
fn call(&self, t: T) -> R;
}
trait FromRequest {
fn from_request() -> Self;
}
// 支持空参数的函数当作 Handler 传递
impl<F, R> Handler<(), R> for F
where
F: Fn() -> R + 'static,
{
fn call(&self, (): ()) -> R {
(self)()
}
}
// 支持一个参数的函数当作 Handler 传递
impl<F, A, R> Handler<(A,), R> for F
where
F: Fn(A) -> R + 'static,
{
fn call(&self, (A,): (A,)) -> R {
(self)(A)
}
}
// 支持两个参数的函数作为 Handler 传递
impl<F, A, B, R> Handler<(A, B), R> for F
where
F: Fn(A, B) -> R + 'static,
{
fn call(&self, (A, B): (A, B)) -> R {
(self)(A, B)
}
}
// 支持 0 参数变成 Tuple 后的 from_request 调用
impl FromRequest for () {
fn from_request() -> () {
()
}
}
// 支持一个参数变成 Tuple 后的 from_request 调用
impl<A> FromRequest for (A, )
where A: FromRequest
{
fn from_request() -> (A,) {
(A::from_request(), )
}
}
// 支持两个参数变成 Tuple 后的 from_request 调用
impl<A, B> FromRequest for (A, B)
where
A: FromRequest,
B: FromRequest,
{
fn from_request() -> (A, B) {
(A::from_request(), B::from_request())
}
}
// 委托调用函数,对被委托的函数参数进行解析后调用
fn handle<T, R, F>(handler: F) -> R
where
F: Handler<T, R>,
T: FromRequest + 'static,
R: 'static,
{
handler.call(T::from_request())
}
// 对 i32 实现 FromRequest 支持参数提取
impl FromRequest for i32 {
fn from_request() -> i32 {
3
}
}
fn test0() -> i32 {
0
}
fn test1(v: i32) -> i32 {
println!("{}", v);
v
}
fn test2(v: i32, v2: i32) -> i32 {
v + v
}
fn main() {
handle(test0);
handle(test1);
handle(test2);
}
基本思路就是:
- 通过一个委托调用的函数,接收一个
trait Object Handler
来抹掉变长参数; - 为不同长度的参数的函数类型全部实现
Handler
,并将参数变成 Tuple单一参数(通过宏生成); FromRequest
实现不同长度 Tuple(通过宏生成),这样可以保证不同长度的参数变成 Tuple 后from_request
可正常调用。