Rust in Everything
基于 tower 0.5.3

Layer 与 Stack

主线位置:两条主线开始之前的装配阶段。Layer 不参与运行时的 poll_ready / call,它决定谁套在谁外面,也就决定了就绪主线往里传、请求主线往外包时经过各层的顺序。

flowchart TB
  svc[服务 svc] --> il["inner.layer(svc)"]
  il --> mid["Inner 包好的服务"]
  mid --> ol["outer.layer(...)"]
  ol --> out["Outer 在最外层"]

tower-layer 是一个 699 行的小 crate,五个文件:lib.rs 定义 Layer,stack.rs、identity.rs、layer_fn.rs 各一个类型,tuple.rs 给元组实现 Layer。tower 在 tower::layer 里原样重新导出它们(Identity、Stack 放在 tower::layer::util 下)。

Layer:服务进,服务出

Layer:

pub trait Layer<S> {
    /// The wrapped service
    type Service;
    /// Wrap the given service with the middleware, returning a new service
    /// that has been decorated with the middleware.
    fn layer(&self, inner: S) -> Self::Service;
}

三点:

  • 没有任何 Service 约束。 S 不要求是服务,Self::Service 也不要求是服务。Layer 在类型上就是一个 Fn(S) -> Service。约束推迟到真正用这个服务的地方才检查,这既让任何中间件都能叠在一起,也让类型错误报得很晚,下一章的 check_service 就是为此准备的。
  • &self。 一个 layer 可以包任意多个服务。TimeoutLayer 里只存一个 Duration,每次 layer 都造一个新的 Timeout<S>。
  • 一个 Layer 通常配一个 Service。 惯例是 XxxLayer 存配置,Xxx<S> 存配置加内层服务并实现 Service。

&T 也是 Layer(第 108 行),原样转发给 T。后面元组的实现靠它递归。

📝NOTE
冷知识:Layer 原来带着 Service 的全部约束

2019 年 4 月以前,Layer 长这样:pub trait Layer<S, Request>,有 Response、Error、LayerError 三个关联类型,Service 必须是 Service<Request, Response = Self::Response, Error = Self::Error>,layer 返回 Result<Self::Service, Self::LayerError>。

#251(「Allow ServiceBuilder to map requests」)把这些全删了。PR 说明:当时 ServiceBuilder 放不进改变请求类型的 layer,于是「All bounds are removed from the Layer trait, essentially reducing it to Fn(T) -> U (a trait is needed as one cannot impl Fn by hand). Bounds are delayed to the last possible moment.」第一个包含它的 tower 版本是 0.1.0。

今天的 Layer<S> 只剩一个关联类型 Service,ServiceBuilder::map_request 这种把 R1 变成 R2 的 layer 才能和别的 layer 叠在一起。

Stack:先 Inner,后 Outer

Stack 把两个 layer 串起来,它自己也是 layer(第 41 行):

pub struct Stack<Inner, Outer> {
    inner: Inner,
    outer: Outer,
}

impl<S, Inner, Outer> Layer<S> for Stack<Inner, Outer>
where
    Inner: Layer<S>,
    Outer: Layer<Inner::Service>,
{
    type Service = Outer::Service;

    fn layer(&self, service: S) -> Self::Service {
        let inner = self.inner.layer(service);

        self.outer.layer(inner)
    }
}

Inner 先包,离服务近;Outer 再包,在外面。约束也是这样串的:Inner 要能包 S,Outer 要能包 Inner 产出的东西。tag 里 Stack 的文档用数字举了例子:inner 是 +2、outer 是 *2,Stack::new(inner, outer).layer(4) 是 (4 + 2) * 2 = 12。

Stack 的 Debug 不用 debug_struct,而是先打印 outer 再打印 inner,用逗号连起来。注释解释了原因:Stack 通常嵌套很深,而且在 ServiceBuilder 里,这样打印出来是一个平铺的列表,「the order of [outer, inner] is important, since it reflects the order that the layers were added to the stack」。为什么 outer 是先加的,见 ServiceBuilder。

📝NOTE
没有蠢问题

问: Stack<Inner, Outer> 里的 Inner,是不是内层的服务?

答: 不是。两个参数都是 layer,服务是 layer(service) 调用时才传进来的。Inner 只是「先套上去、离服务更近的那个 layer」。ServiceBuilder 里每加一个 layer,新 layer 都放在 Inner 的位置,之前攒下的整串放在 Outer 的位置,所以 ServiceBuilder<Stack<ConcurrencyLimitLayer, Stack<TimeoutLayer, Identity>>> 包出来的是 Timeout<ConcurrencyLimit<S>>,类型名里的顺序和包出来的顺序是反的。

Identity:什么也不做

Identity 的 layer 原样返回(第 39 行):

impl<S> Layer<S> for Identity {
    type Service = S;

    fn layer(&self, inner: S) -> Self::Service {
        inner
    }
}

它有两个用处:ServiceBuilder::new() 从 Identity 开始往上叠;option_layer(None) 用它占位,让「有这一层」和「没有这一层」是同一个类型(Either<L, Identity>)。

layer_fn:闭包当 Layer

layer_fn 把一个 Fn(S) -> Out 包成 LayerFn:

pub fn layer_fn<T>(f: T) -> LayerFn<T> {
    LayerFn { f }
}

#[derive(Clone, Copy)]
pub struct LayerFn<F> {
    f: F,
}

impl<F, S, Out> Layer<S> for LayerFn<F>
where
    F: Fn(S) -> Out,
{
    type Service = Out;

    fn layer(&self, inner: S) -> Self::Service {
        (self.f)(inner)
    }
}

要的是 Fn 而不是 FnOnce,因为 layer 拿的是 &self,同一个 layer 可能被调很多次。写中间件时,Service 实现总得自己写,layer_fn(|inner| MyService { inner, .. }) 省掉的只是那个 XxxLayer 结构体。

元组也是 Layer:顺序和 Stack 相反

tuple.rs 给 () 和 1 到 16 元的元组实现了 Layer,() 等于 Identity。两元的实现(第 23 行):

impl<S, L1, L2> Layer<S> for (L1, L2)
where
    L1: Layer<L2::Service>,
    L2: Layer<S>,
{
    type Service = L1::Service;

    fn layer(&self, service: S) -> Self::Service {
        let (l1, l2) = self;
        l1.layer(l2.layer(service))
    }
}

L1 在最外层,和读代码的顺序一致。三元及以上是递归的(第 36 行):l1.layer((l2, l3).layer(service)),这里的 (l2, l3) 是两个引用组成的元组,所以要靠上面 &T 的实现才能成立。17 元以上没有实现。

写法结果最外层
Stack::new(a, b)B(A(svc))第二个参数
(a, b)A(B(svc))第一个元素
ServiceBuilder::new().layer(a).layer(b)A(B(svc))先写的

用三个 layer_fn 验证一下,每个 layer 把字符串包一层:

tower = { version = "=0.5.3" }
use tower::layer::util::{Identity, Stack};
use tower::layer::{layer_fn, Layer};

fn main() {
  let a = layer_fn(|s: String| format!("A({s})"));
  let b = layer_fn(|s: String| format!("B({s})"));
  let c = layer_fn(|s: String| format!("C({s})"));

  println!("Stack::new(a, b): {}", Stack::new(a, b).layer("svc".to_string()));
  println!("(a, b, c):        {}", (a, b, c).layer("svc".to_string()));
  println!("Identity:         {}", Identity::new().layer("svc".to_string()));
  println!("&a:               {}", (&a).layer("svc".to_string()));
}

M2 Max、rustc 1.99.0:

Stack::new(a, b): B(A(svc))
(a, b, c):        A(B(C(svc)))
Identity:         svc
&a:               A(svc)

这里的「服务」只是一个 String,正好说明 Layer 不要求它是服务。a、b、c 能重复使用,是因为不捕获变量的闭包是 Copy,LayerFn 也派生了 Copy。

❗IMPORTANT
动脑筋:Stack 里套元组依赖和上面相同。
use tower::layer::util::{Identity, Stack};
use tower::layer::{layer_fn, Layer};

fn main() {
  let a = layer_fn(|s: String| format!("A({s})"));
  let b = layer_fn(|s: String| format!("B({s})"));
  let c = layer_fn(|s: String| format!("C({s})"));

  let l = Stack::new((a, b), Stack::new(c, Identity::new()));
  println!("{}", l.layer("svc".to_string()));
}
打印出来的是哪一行?

动脑筋答案

M2 Max、rustc 1.99.0:

C(A(B(svc)))
  • 外层 Stack 的 Inner 是元组 (a, b),先用它包:元组里 a 在外,得到 A(B(svc))。
  • 外层 Stack 的 Outer 是 Stack::new(c, Identity::new()),再用它包:它先用 c 包、再用 Identity 包(不变),得到 C(A(B(svc)))。

小结

  • Layer<S> 只有一个关联类型 Service 和一个 layer(&self, S),没有任何 Service 约束;约束推迟到用的时候才检查。
  • Stack<Inner, Outer> 先用 Inner 包、再用 Outer 包,Outer 在外面。它的 Debug 先打 outer 再打 inner,正好是添加顺序。
  • Identity 原样返回,是 ServiceBuilder 的起点,也是 option_layer(None) 的占位。
  • layer_fn 把 Fn(S) -> Out 变成 Layer,LayerFn 是 Clone + Copy。
  • () 和 1 到 16 元的元组都是 Layer,第一个元素在最外层,和 Stack::new 的参数顺序相反。

💬 关联讨论

发起讨论
加载中...