https://rustasync.github.io/team/2018/11/27/tide-middleware-evolution.html
自从Tide上一篇文章发布以来,已经有一群新贡献者做出了许多优秀的贡献!在这篇文章中,我想谈谈@tirr-c为显著改进中间件所做的工作。
一如既往:如果您对这些话题感兴趣,我们希望您能帮助我们建设Tide!有一个活跃的开放问题管道,其中包括标记为良好启动问题的问题,我们希望在0.1版本中看到的内容正在进行讨论。现在就参与进来是帮助确定这个社区构建框架的方向的最佳机会!
改进中间件特性(Improving the Middleware trait)
在上一篇文章中,我们借鉴了actix-web设计,提出了before/after-style中间件:
pub trait Middleware<Data>: Send + Sync {
/// Asynchronously transform the incoming request, or abort further handling by immediately
/// returning a response.
fn request(
&self,
data: &mut Data,
req: Request,
params: &RouteMatch<'_>,
) -> FutureObj<'static, Result<Request, Response>>;
/// Asynchronously transform the outgoing response.
fn response(
&self,
data: &mut Data,
head: &Head,
resp: Response,
) -> FutureObj<'static, Response>;
}
然而,自那以后,@tirr-c认识到,如果对核心特性使用“around”设计,将会有很大的收获:
/// Middleware that wraps around remaining middleware chain.
pub trait Middleware<Data>: Send + Sync {
/// Asynchronously handle the request, and return a response.
fn handle<'a>(&'a self, ctx: RequestContext<'a, Data>) -> FutureObj<'a, Response>;
}
这个新接口是使用一个方便的类型RequestContext构建的,它封装了所有可以使用的信息中间件:
pub struct RequestContext<'a, Data> {
pub app_data: Data,
pub req: Request,
pub params: RouteMatch<'a>,
// plus additional, private fields
}
impl<'a, Data: Clone + Send> RequestContext<'a, Data> {
/// Consume this context, and run remaining middleware chain to completion.
pub fn next(self) -> FutureObj<'a, Response> { ... }
}
在这种方法中,每个中间件都被赋予对剩余的请求处理管道的完全控制。但是请记住,中间件和端点在路由之后严格运行,因此管道必须返回响应。(内部重定向还有一个悬而未决的问题。)
值得注意的是,在这个接口上构建样式为before/after的中间件构造函数非常简单,因此我们不会失去这种便利。但是使用“around”中间件作为核心接口有一些关键优势:
- 在管道其余部分之前和之后执行的步骤之间通信数据要简单得多。在最初的提议中,您必须使用Request::extensions typemap来注入信息以供以后提取,而这些信息必须是“静态的”。使用around中间件,您所需要的只是一个let绑定,而该绑定可以包含一些借来的内容,这些内容将持续到管道的其余部分执行之后。
- 新接口可以说更简单、更整洁。
感谢@tirr-c解决了这一切!
带有定制中间件的嵌套路由器
在上一篇文章中,中间件只能应用于顶层,因此所有端点将使用完全相同的中间件。但是,引入只适用于路由子集的中间件可能会很有用。通常,这样的定制组根据它们的路径结构进行路由,这也是我们在Tide中采用的方法。
要将中间件应用到具有公共前缀的路由子集,可以使用nest:
let mut app = App::new(your_data);
app.at("/some/prefix").nest(|r| {
r.middleware(some_middleware); // applies to everything under `/some/prefix`
r.at("/").get(prefix_top_endpoint); // matches `/some/prefix`
r.at("/foo").get(foo_endpoint); // matches `/some/prefix/foo`
});
// no middleware is applied to this route
app.at("/").get(index_endpoint);
app.serve(address);
nest方法可以让你对你选择的前缀下嵌套的子程序进行可变访问:
impl<'a, Data> Resource<'a, Data> {
/// "Nest" a subrouter to the path.
///
/// This method will build a fresh `Router` and give a mutable reference to it to the builder
/// function. Builder can set up a subrouter using the `Router`. All middleware applied inside
/// the builder will be local to the subrouter and its descendents.
pub fn nest(self, builder: impl FnOnce(&mut Router<Data>));
}
我们预计,随着时间的推移,相同的嵌套设置将有许多其他用途,包括配置。
再次感谢@tirr-c对该设计进行了多次迭代!
计算值:一个例子
最后,我们现在有一个使用计算值进行cookie解析的完整示例,您可以在这里找到!正在进行的工作的一个非常重要的领域将是查看有多少传统中间件可以表示为计算值。这样做使得推理更加容易(因为计算值的功能不那么强大),并且消除了一些常见的中间件陷阱(顺序依赖等)。
我们计划将这个计算值转移到Tide中,如果能积累许多其他类似的构建块,那就太好了;PRs非常欢迎!
完毕;
最后放出 Tide的GitHub的地址
https://github.com/rustasync/tide
网友评论