1. 背景

Web 服务的架构通常是:

当前最流行的 Reverse Proxy 是 Nginx 及其变种,比如 Openresty、Tengine 等。对于每种语言生态,都有其流行的 Web Server 和专门的称谓,比如:

在各种编程语言的世界中,流行的 Web Framework 更是多种多样,以我了解到的为例:

不仅 Framework 分层,整个 Architecture 其实也是分层结构,每层各司其职。

本文要谈的内容从 Web Framework 引申而出。假设我们自己设计实现一个 Web Framework,那么应该给开发人员提供路由注册接口,所谓的路由是指 URI Pattern 与 Handler Function 之间的映射关系,比如:

Handler Function 由 Framework 的使用人员实现,其必须满足 Framework 的规范。接下来看几个示例。

1. Python FastAPI:

2. Golang Gin:

可以看出,在 Python FastAPI 中 Handler Function 的签名非常随意。如果我们是 FastAPI 的开发者,那么我们可以在处理请求时,通过反射获取函数的签名,然后根据 Request Context 以及函数签名中的形参名、Type Annotation 等信息生成实参列表,最后再调用处理函数,得到响应数据。

在 Golang Gin 中 Handler Function 的类型必须是 func(*Context),开发人员通过 Context 对象获取其需要的信息。

有了上面的铺垫,下面我们进入本文的主题。我们先使用 Rust Actix Web 开发一个与前面类似的 Web Server。

src/main.rs:

Cargo.toml:

可以看到 Rust Actix Web 中的 Handler Function 与 Python FastAPI 中的类似,可以拥有任意数量(小于 12 个)、任意类型(必须实现 actix_web::FromRequest Trait)的参数。那么 Actix Web 是如何调用这些用户自定义的、拥有任意参数的处理函数的呢?像 Python 那样通过反射么?答案是不是。


2. 函数指针与闭包

函数的类型是 fn(注意是小写 f,而不是 FnFn 是一个闭包 Trait),fn 被称为函数指针(Function Pointer)。通过函数指针,我们可以将一个函数作为另一个函数的参数。比如:

函数指针实现了所有闭包 Trait(FnFnMutFnOnce)。因此 add_one 的类型是 fn(i32) -> i32,实现了 Fn(i32) -> i32 Trait,所以 add_oneFn(i32) -> i32 Trait 的一个实现的一个实例。

接下来看另外一个常见的问题 - 如何返回闭包。

闭包表现为 Trait,这意味着不能直接返回闭包。对于大部分需要返回 Trait 的情况,可以使用实现 Trait 的具体类型作为函数的返回值。但是这不能用于闭包,因为它们没有具体类型。比如:

编译器给出的错误是:

解决方法是使用 Trait Object:


3. 使用元组参数调用函数

对于下面的函数:

调用 add(1, 2) 不会报错。但是我们使用元组 (1, 2) 调用 add,将得到错误:error[E0061]: this function takes 2 arguments but 1 argument was supplied

如果非要用元组参数的形式调用函数,那么可以这样做:

在上面的代码中,我们为所有拥有 2 个参数的函数实现 Handler Trait。call_it 方法接受一个元组作为参数,并且在形参列表中,对该元组进行解构(Destructure);在方法体中,用该元组的所有成员调用 selfself 代表所有拥有 2 个参数的函数,比如 add)。

再进一步,我们使用 Macro 为所有拥有 0 个、1 个、2 个、...、12 个参数的函数实现 Handler Trait,其中 call_it 方法的逻辑与上面类似:

上段代码来自于:actix-web


4. 生成元组类型的值

接下来需要生成元组类型的值,以调用函数。下面仍然先以带 2 个参数的函数为例,最后再用宏进行抽象:

上面的代码为二元组类型 (T1, T2)T1T2 都必须实现 FromRequest Trait)实现 FromRequest Trait。在 from_request 方法体中分别调用 T1::from_requestT2::from_request 生成元组的成员。

再进一步,我们为所有拥有 1 个、2 个、...、12 个成员的元组(每个成员都必须实现 FromRequest Trait)实现 FromRequest Trait,其中 from_request 方法的逻辑与上面类似:

上述代码参考自:actix-web


5. 注册和管理 Handler Function

我们用下面的函数将 Handler Function 的逻辑封装进闭包,以对外提供统一的接口

通常,我们有多个处理函数,可以将封装后的处理函数放到容器数据结构中进行统一管理。比如:


6. 完整示例