背景

今天看了一下 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);
}

基本思路就是:

  1. 通过一个委托调用的函数,接收一个 trait Object Handler 来抹掉变长参数;
  2. 为不同长度的参数的函数类型全部实现 Handler,并将参数变成 Tuple单一参数(通过宏生成);
  3. FromRequest 实现不同长度 Tuple(通过宏生成),这样可以保证不同长度的参数变成 Tuple 后 from_request 可正常调用。

源码参见