Web 服务的架构通常是:
|->Web Server->Framework->Our Func
Client->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 之间的映射关系,比如:
/foo foo_handler
/bar bar_handler
Handler 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 Func
where
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 的逻辑封装进闭包,以对外提供统一的接口:
xxxxxxxxxx
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()))
}
通常,我们有多个处理函数,可以将封装后的处理函数放到容器数据结构中进行统一管理。比如:
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));
xxxxxxxxxx
use 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);
}