【问题标题】:How to implement a dynamic list of "middleware" functions?如何实现“中间件”功能的动态列表?
【发布时间】:2023-05-07 12:56:02
【问题描述】:

我正在编写一些结构类似于 Web 框架中的“中间件”的代码,因此我将在这个最小示例中使用该术语。中间件函数的思想是它包装了实际的响应处理程序,因此它可以在请求进入处理程序之前修改请求,并在响应返回后修改响应:

struct Request;
struct Response;

trait Handler {
    fn handle(&self, request: Request) -> Response;
}

trait Middleware {
    fn handle(&self, request: Request, next: Box<dyn FnOnce(Request) -> Response>) -> Response;
}

我考虑过使用两个独立的功能,一个用于预处理请求,一个用于后处理响应,但是如果不使用 hack,我将无法在请求期间存储任何其他状态。

服务器包含动态配置的处理程序和中间件,因此它必须使用一些装箱的特征对象:

struct Server {
    handler: Box<dyn Handler>,
    middlewares: Vec<Box<dyn Middleware>>,
}

现在,如何实现响应处理?这是我的两次尝试:

impl Server {
    // First attempt: using Iterator::fold()
    fn handle_request_fold<'a>(&'a self, request: Request) -> Response {
        let handler_with_middlewares = self.middlewares.iter()
            .rev()
            .fold::<Box<dyn FnOnce(Request) -> Response + 'a>, _>(
                Box::new(|request| self.handler.handle(request)),
                |next, middleware| {
                    Box::new(|request| middleware.handle(request, next))
                }
            );
        handler_with_middlewares(request)
    }
    
    // Second attempt: using recursion
    fn handle_request_recurse(&self, request: Request) -> Response {
        self.handle_request_from(request, 0)
    }
    
    fn handle_request_from<'a>(&'a self, request: Request, index: usize) -> Response {
        if index >= self.middlewares.len() {
            self.handler.handle(request)
        } else {
            let next = Box::new(
                |r: Request| self.handle_request_from(r, index + 1));
            self.middlewares[index].handle(request, next)
        }
    }
}

两次尝试都给出了相同的错误,这表明我在这里做的是根本错误的事情:

error[E0759]: `self` has lifetime `'a` but it needs to satisfy a `'static` lifetime requirement
  --> src/lib.rs:25:67
   |
19 |     fn handle_request_fold<'a>(&'a self, request: Request) -> Response {
   |                                -------- this data with lifetime `'a`...
...
25 |                     Box::new(|request| middleware.handle(request, next))
   |                                                                   ^^^^ ...is captured and required to live as long as `'static` here

我知道 trait 对象有一个隐式的 'static 生命周期,所以我尝试添加显式生命周期,如您所见,但没有帮助。我不明白为什么编译器会在此处的任何地方要求 'static 生命周期;没有任何东西(包括闭包)可以逃脱handle_request_* 函数,对吧?

Playground link

【问题讨论】:

  • 有必要让中间件接受动态函数对象吗?我没有看到全貌,但对我来说,在构建中间件后不太可能需要热交换函数。如果你需要另一个功能,你可以制作另一个中间件(这样你就可以放弃泛型,而无需在通过中间件的过程中生成数百万个盒子)
  • @AlexLarionov 这实际上不是我正在开发的网络应用程序,而是数据处理管道。配置(“中间件”和“处理程序”列表)在运行时从 YAML 文件加载。每个都只调用一次,因此装箱的开销可以忽略不计。

标签: rust


【解决方案1】:

在发布之后,我发现:传递给Middleware 的盒装特征对象也需要非'static 生命周期。

trait Middleware {
    fn handle<'a>(&self, request: Request,
                  next: Box<dyn FnOnce(Request) -> Response + 'a>)
        -> Response;
}
    fn handle_request_from(&self, request: Request, index: usize) -> Response {
        if index >= self.middlewares.len() {
            self.handler.handle(request)
        } else {
            let next = Box::new(
                move |request| self.handle_request_from(request, index + 1));
            self.middlewares[index].handle(request, next)
        }
    }

【讨论】: