Web 服务的架构通常是:
|->Web Server->Framework->Our FuncClient->Proxy-|->Web Server->Framework->Our Func|->Web Server->Framework->Our Func
当前最流行的 Reverse Proxy 是 Nginx 及其变种,比如 Openresty、Tengine 等。对于每种语言生态,都有其流行的 Web Server 和专门的称谓,比如:
在各种编程语言的世界中,流行的 Web Framework 更是多种多样,以我了解到的为例:
不仅 Framework 分层,整个 Architecture 其实也是分层结构,每层各司其职。
本文要谈的内容从 Web Framework 引申而出。假设我们自己设计实现一个 Web Framework,那么应该给开发人员提供路由注册接口,所谓的路由是指 URI Pattern 与 Handler Function 之间的映射关系,比如:
/foofoo_handler/barbar_handlerHandler Function 由 Framework 的使用人员实现,其必须满足 Framework 的规范。接下来看几个示例。
import typing
import fastapi
app = fastapi.FastAPI()
.get("/{name}")def hello(name: str, user_agent: typing.Optional[str] = fastapi.Header(None)): return fastapi.responses.JSONResponse( content={ "name": name, "user_agent": user_agent }, status_code=200)
if __name__ == "__main__": import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)2. Golang Gin:
package main
import ( "net/http"
"github.com/gin-gonic/gin")
func setupRouter() *gin.Engine { r := gin.Default()
// 获取用户数据路由 r.GET("/:name", func(c *gin.Context) { name := c.Params.ByName("name") c.JSON(http.StatusOK, gin.H{ "name": name, "user_agent": c.Request.UserAgent(), }) })
return r}
func main() { r := setupRouter() r.Run(":8080")}可以看出,在 Python FastAPI 中 Handler Function 的签名非常随意。如果我们是 FastAPI 的开发者,那么我们可以在处理请求时,通过反射获取函数的签名,然后根据 Request Context 以及函数签名中的形参名、Type Annotation 等信息生成实参列表,最后再调用处理函数,得到响应数据。
在 Golang Gin 中 Handler Function 的类型必须是 func(*Context),开发人员通过 Context 对象获取其需要的信息。
有了上面的铺垫,下面我们进入本文的主题。我们先使用 Rust Actix Web 开发一个与前面类似的 Web Server。
src/main.rs:
use actix_web::{get, web, App, HttpRequest, HttpResponse, HttpServer, Responder};use serde::{Deserialize, Serialize};
async fn index() -> impl Responder { HttpResponse::Ok().body("Hello, World!")}
pub struct Hello { name: String, user_agent: String,}
async fn hello(name: web::Path<String>, req: HttpRequest) -> impl Responder { let user_agent = req .headers() .get("User-Agent") .unwrap() .to_str() .unwrap() .to_string(); HttpResponse::Ok().json(Hello { name: name.into_inner(), user_agent, })}
async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .service(index) .route("/{name}", web::get().to(hello)) }) .bind("0.0.0.0:8080")? .run() .await}Cargo.toml:
...
[dependencies]actix-web = "4"serde = { version = "1.0", features = ["derive"] }可以看到 Rust Actix Web 中的 Handler Function 与 Python FastAPI 中的类似,可以拥有任意数量(小于 12 个)、任意类型(必须实现 actix_web::FromRequest Trait)的参数。那么 Actix Web 是如何调用这些用户自定义的、拥有任意参数的处理函数的呢?像 Python 那样通过反射么?答案是不是。
函数的类型是 fn(注意是小写 f,而不是 Fn,Fn 是一个闭包 Trait),fn 被称为函数指针(Function Pointer)。通过函数指针,我们可以将一个函数作为另一个函数的参数。比如:
fn add_one(x: i32) -> i32 { x + 1}
fn add_twice(f: fn(i32) -> i32, arg: i32) -> i32 { f(arg) + f(arg)}
fn main() { let answer = add_twice(add_one, 5);
println!("The answer is {}", answer); // The answer is 12}函数指针实现了所有闭包 Trait(Fn、FnMut、FnOnce)。因此 add_one 的类型是 fn(i32) -> i32,实现了 Fn(i32) -> i32 Trait,所以 add_one 是 Fn(i32) -> i32 Trait 的一个实现的一个实例。
接下来看另外一个常见的问题 - 如何返回闭包。
闭包表现为 Trait,这意味着不能直接返回闭包。对于大部分需要返回 Trait 的情况,可以使用实现 Trait 的具体类型作为函数的返回值。但是这不能用于闭包,因为它们没有具体类型。比如:
fn return_closure() -> dyn Fn(i32) -> i32 { |x| x + 1}编译器给出的错误是:
|1 | fn return_closure() -> dyn Fn(i32) -> i32 {| ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
解决方法是使用 Trait Object:
fn return_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1)}对于下面的函数:
fn add(x: i32, y: i32) -> i32 { x + y}调用 add(1, 2) 不会报错。但是我们使用元组 (1, 2) 调用 add,将得到错误:error[E0061]: this function takes 2 arguments but 1 argument was supplied。
如果非要用元组参数的形式调用函数,那么可以这样做:
pub trait Handler<Args, Res>: Clone + 'static { fn call_it(&self, args: Args) -> Res;}
impl<Func, Arg1, Arg2, Res> Handler<(Arg1, Arg2), Res> for Funcwhere Func: Fn(Arg1, Arg2) -> Res + Clone + 'static,{ fn call_it(&self, (Arg1, Arg2): (Arg1, Arg2)) -> Res { (self)(Arg1, Arg2) }}
fn add(x: i32, y: i32) -> i32 { x + y}
fn main() { println!("{}", add(1, 2)); // 3 println!("{}", add.call_it((1, 2))); // 3}在上面的代码中,我们为所有拥有 2 个参数的函数实现 Handler Trait。call_it 方法接受一个元组作为参数,并且在形参列表中,对该元组进行解构(Destructure);在方法体中,用该元组的所有成员调用 self(self 代表所有拥有 2 个参数的函数,比如 add)。
再进一步,我们使用 Macro 为所有拥有 0 个、1 个、2 个、...、12 个参数的函数实现 Handler Trait,其中 call_it 方法的逻辑与上面类似:
x
pub trait Handler<Args>: Clone + 'static { fn call_it(&self, args: Args);}
macro_rules! factory_tuple ({ $($param:ident)* } => { impl<Func, $($param,)*> Handler<($($param,)*)> for Func where Func: Fn($($param),*) + Clone +'static, { fn call_it(&self, ($($param,)*): ($($param,)*)) { (self)($($param,)*); } }});
factory_tuple! {}factory_tuple! { A }factory_tuple! { A B }factory_tuple! { A B C }factory_tuple! { A B C D }factory_tuple! { A B C D E }factory_tuple! { A B C D E F }factory_tuple! { A B C D E F G }factory_tuple! { A B C D E F G H }factory_tuple! { A B C D E F G H I }factory_tuple! { A B C D E F G H I J }factory_tuple! { A B C D E F G H I J K }factory_tuple! { A B C D E F G H I J K L }上段代码来自于:actix-web
接下来需要生成元组类型的值,以调用函数。下面仍然先以带 2 个参数的函数为例,最后再用宏进行抽象:
x
pub struct Error(String);
pub trait FromRequest: Sized { fn from_request(req: &String) -> Result<Self, Error>;}
impl<T1: FromRequest + 'static, T2: FromRequest + 'static> FromRequest for (T1, T2) { fn from_request(req: &String) -> Result<Self, Error> { Ok((T1::from_request(&req).ok().unwrap(), T2::from_request(&req).ok().unwrap())) }}
struct OriginRequest(String);
impl FromRequest for OriginRequest { fn from_request(req: &String) -> Result<Self, Error> { Ok(OriginRequest(req.clone())) }}
struct RequestLength(usize);
impl FromRequest for RequestLength { fn from_request(req: &String) -> Result<Self, Error> { Ok(RequestLength(req.len())) }}上面的代码为二元组类型 (T1, T2)(T1、T2 都必须实现 FromRequest Trait)实现 FromRequest Trait。在 from_request 方法体中分别调用 T1::from_request、T2::from_request 生成元组的成员。
再进一步,我们为所有拥有 1 个、2 个、...、12 个成员的元组(每个成员都必须实现 FromRequest Trait)实现 FromRequest Trait,其中 from_request 方法的逻辑与上面类似:
x
pub struct Error(String);
pub trait FromRequest: Sized { fn from_request(req: &String) -> Result<Self, Error>;}
macro_rules! tuple_from_req { ( $($T: ident),* ) => { impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+) { fn from_request(req: &String) -> Result<Self, Error> { Ok( ( $( $T::from_request(&req).ok().unwrap(), )+ ) ) } } };}
tuple_from_req! { A }tuple_from_req! { A, B }tuple_from_req! { A, B, C }tuple_from_req! { A, B, C, D }tuple_from_req! { A, B, C, D, E }tuple_from_req! { A, B, C, D, E, F }tuple_from_req! { A, B, C, D, E, F, G }tuple_from_req! { A, B, C, D, E, F, G, H }tuple_from_req! { A, B, C, D, E, F, G, H, I }tuple_from_req! { A, B, C, D, E, F, G, H, I, J }tuple_from_req! { A, B, C, D, E, F, G, H, I, J, K }tuple_from_req! { A, B, C, D, E, F, G, H, I, J, K, L }上述代码参考自:actix-web
我们用下面的函数将 Handler Function 的逻辑封装进闭包,以对外提供统一的接口:
xxxxxxxxxxpub fn new_handler<F, Args>(f: F) -> Box<dyn Fn(&String)>where F: Handler<Args>, Args: FromRequest,{ let f = f.clone(); Box::new(move |req: &String| f.call_it(Args::from_request(&req).ok().unwrap()))}通常,我们有多个处理函数,可以将封装后的处理函数放到容器数据结构中进行统一管理。比如:
x
let mut handlers: HashMap<String, Box<dyn Fn(&String)>> = HashMap::new();handlers.insert("/foo".to_string(), new_handler(foo_handler));handlers.insert("/bar".to_string(), new_handler(bar_handler));xxxxxxxxxxuse std::collections::HashMap;
pub trait Handler<Args>: Clone + 'static { fn call_it(&self, args: Args);}
macro_rules! factory_tuple ({ $($param:ident)* } => { impl<Func, $($param,)*> Handler<($($param,)*)> for Func where Func: Fn($($param),*) + Clone +'static, { fn call_it(&self, ($($param,)*): ($($param,)*)) { (self)($($param,)*); } }});
factory_tuple! {}factory_tuple! { A }factory_tuple! { A B }factory_tuple! { A B C }factory_tuple! { A B C D }factory_tuple! { A B C D E }factory_tuple! { A B C D E F }factory_tuple! { A B C D E F G }factory_tuple! { A B C D E F G H }factory_tuple! { A B C D E F G H I }factory_tuple! { A B C D E F G H I J }factory_tuple! { A B C D E F G H I J K }factory_tuple! { A B C D E F G H I J K L }
pub struct Error(String);
pub trait FromRequest: Sized { fn from_request(req: &String) -> Result<Self, Error>;}
macro_rules! tuple_from_req { ( $($T: ident),* ) => { impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+) { fn from_request(req: &String) -> Result<Self, Error> { Ok( ( $( $T::from_request(&req).ok().unwrap(), )+ ) ) } } };}
tuple_from_req! { A }tuple_from_req! { A, B }tuple_from_req! { A, B, C }tuple_from_req! { A, B, C, D }tuple_from_req! { A, B, C, D, E }tuple_from_req! { A, B, C, D, E, F }tuple_from_req! { A, B, C, D, E, F, G }tuple_from_req! { A, B, C, D, E, F, G, H }tuple_from_req! { A, B, C, D, E, F, G, H, I }tuple_from_req! { A, B, C, D, E, F, G, H, I, J }tuple_from_req! { A, B, C, D, E, F, G, H, I, J, K }tuple_from_req! { A, B, C, D, E, F, G, H, I, J, K, L }
struct OriginRequest(String);
impl FromRequest for OriginRequest { fn from_request(req: &String) -> Result<Self, Error> { Ok(OriginRequest(req.clone())) }}
struct RequestLength(usize);
impl FromRequest for RequestLength { fn from_request(req: &String) -> Result<Self, Error> { Ok(RequestLength(req.len())) }}
fn foo_handler(or: OriginRequest, rl: RequestLength) { println!( "foo_handler: Origin Request is {:?}, length is {:?}", or, rl );}
fn bar_handler(rl: RequestLength) { println!("bar_handler: Request length is {:?}", rl);}
pub fn new_handler<F, Args>(f: F) -> Box<dyn Fn(&String)>where F: Handler<Args>, Args: FromRequest,{ let f = f.clone(); Box::new(move |req: &String| f.call_it(Args::from_request(&req).ok().unwrap()))}
fn main() { // 注册 Handler Function let mut handlers: HashMap<String, Box<dyn Fn(&String)>> = HashMap::new(); handlers.insert("/foo".to_string(), new_handler(foo_handler)); handlers.insert("/bar".to_string(), new_handler(bar_handler));
// 假设第一个请求是:使用 "first request" 访问 /foo let req = "first request".to_string(); handlers.get("/foo").unwrap()(&req);
// 假设第二个请求是:使用 "second request" 访问 /bar let req = "second request".to_string(); handlers.get("/bar").unwrap()(&req);}