Filter
主线位置:请求主线。
poll_ready原样转发;Filter在call里检查请求,AsyncFilter把检查放进响应 future。
flowchart TB
call["call(req)"] --> kind{哪一种}
kind -->|Filter| sc["predicate.check(req)"]
sc -->|"Ok(req2)"| ic["inner.call(req2)"]
sc -->|"Err(e)"| rej["立即完成的 Err"]
kind -->|AsyncFilter| mv[换出已就绪的内层]
mv --> fut["future:Check 状态"]
fut -->|"Ok(req2)"| wc["inner.call(req2)"]
wc --> wr[WaitResponse 状态]
fut -->|"Err(e)"| rej2[返回 Err]
Filter 回答一个问题:这个请求要不要交给内层。检查函数叫谓词(predicate),它拿走请求,返回 Ok(新请求) 或 Err(错误)。新请求可以就是原来那个,也可以改过,甚至换了类型。
两个谓词 trait
pub trait AsyncPredicate<Request> {
// ...
type Future: Future<Output = Result<Self::Request, BoxError>>;
// ...
type Request;
// ...
fn check(&mut self, request: Request) -> Self::Future;
}
/// Checks a request synchronously.
pub trait Predicate<Request> {
// ...
type Request;
// ...
fn check(&mut self, request: Request) -> Result<Self::Request, BoxError>;
}
两点:
- 关联类型
Request是检查之后的请求类型。Filter<T, U>实现Service<Request>,要求内层T: Service<U::Request>,所以谓词可以把String解析成u32再交给只收u32的内层。 check拿走请求、按值返回。检查和改写是同一步,不需要先借用检查、再复制一份去改。
两个 trait 都给闭包写了通用实现(Predicate 的、AsyncPredicate 的):FnMut(T) -> Result<R, E> 是同步谓词,FnMut(T) -> impl Future<Output = Result<R, E>> 是异步谓词,E: Into<BoxError> 就行,错误会被装箱。需要带状态的谓词(比如查一个 HashSet)就给自己的类型实现 trait。模块文档的建议是:用不着 .await 就用同步的,开销更小。
#508(0.4.0)之前,tower::filter 的谓词是异步地检查 &Request,返回 Result<(), Error>,只能放行或拒绝。另一个组合子 try_with 则是同步地把 Request 映射成 Result<DifferentRequest, Error>。PR 说明指出两者有重叠,而且「It definitely seems like there's demand for both synchronous and asynchronous request filtering. However, the APIs we have currently differ pretty significantly.」它还提到 linkerd2-proxy 自己写了一个介于两者之间的 RequestFilter:同步、能改请求类型、用 trait 而不是闭包表示谓词。
#508 改成了现在的样子:「Predicates are synchronous by default. A separate AsyncFilter type and an AsyncPredicate trait are available for predicates returning futures.」「Predicates may now return a new Request type, allowing Filter and AsyncFilter to subsume try_map_request.」错误也从固定类型变成了任意能转成 BoxError 的类型。
Filter:call 里检查
Filter 只有 inner 和 predicate 两个字段,#[derive(Clone)]。Service 实现:
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, request: Request) -> Self::Future {
ResponseFuture::new(match self.predicate.check(request) {
Ok(request) => Either::Right(self.inner.call(request).err_into()),
Err(e) => Either::Left(std::future::ready(Err(e))),
})
}
ResponseFuture 是 Either<Ready<Result<R, BoxError>>, ErrInto<F, BoxError>> 外面包一层不透明类型:拒绝时是一个立即完成的错误,通过时是内层 future 加错误转换。没有新的状态机,也没有分配。
poll_ready 原样转发,所以先问的是内层,谓词要等到 call 才跑:调用方为一个最终会被拒绝的请求,同样要等内层就绪。被拒绝时内层已经就绪、但没有被 call,它的就绪状态留在 self.inner 里,下一个请求直接用。比如内层是 ConcurrencyLimit,它取到的许可还在 self.permit 里,不会丢,也不会多占。
ServiceBuilder::filter 和 FilterLayer 生成的就是 Filter。Filter 还把谓词单独暴露成 check 方法,不经过 call 也能检查一个请求。
AsyncFilter:检查放进 future
AsyncFilter 字段相同,Service 实现多了一个约束 T: Clone。call:
fn call(&mut self, request: Request) -> Self::Future {
use std::mem;
let inner = self.inner.clone();
// In case the inner service has state that's driven to readiness and
// not tracked by clones (such as `Buffer`), pass the version we have
// already called `poll_ready` on into the future, and leave its clone
// behind.
let inner = mem::replace(&mut self.inner, inner);
// Check the request
let check = self.predicate.check(request);
AsyncResponseFuture::new(check, inner)
}
异步检查要在 call 返回之后才完成,到那时再调用内层,future 手里就得有一个内层服务。&mut self 的借用活不到 future 里,所以只能 clone 一份放进去。放进去的必须是已经 poll_ready 过的那一份:clone 出来的副本不带就绪状态(注释点名了 Buffer,它的副本不带预留的通道位置)。mem::replace 把新 clone 留在 self.inner,把就绪的那个换出来交给 future。
AsyncResponseFuture 有两个状态,Check 和 WaitResponse,poll:
loop {
match this.state.as_mut().project() {
StateProj::Check { mut check } => {
let request = ready!(check.as_mut().poll(cx))?;
let response = this.service.call(request);
this.state.set(State::WaitResponse { response });
}
StateProj::WaitResponse { response } => {
return response.poll(cx).map_err(Into::into);
}
}
}
检查通过,在 future 里 call 内层;检查失败,? 直接返回错误,future 连同里面那个就绪的内层一起被 drop,内层占的东西(Buffer 的位置、ConcurrencyLimit 的许可)这时归还。代价也在这里:异步检查进行期间,这些东西一直被占着。
问: Filter 不要求内层 Clone,为什么 AsyncFilter 要?
答: 区别在内层 call 发生在哪。Filter 的检查是同步的,在 call 里当场做完,当场用 &mut self.inner 调用内层。AsyncFilter 的检查要 .await,内层的 call 只能推迟到响应 future 里,而响应 future 不能借用 self,只能自己持有一个服务。持有的只能是从 self 换出来的那一个,self 又不能空着,所以要 clone 一个补回去。
实验:改写类型与拒绝
tower = { version = "=0.5.3", features = ["filter", "util"] }
tokio = { version = "=1.54.1", features = ["full", "test-util"] }
use std::num::ParseIntError;
use std::time::Duration;
use tower::{service_fn, BoxError, Service, ServiceBuilder, ServiceExt};
#[tokio::main(flavor = "current_thread", start_paused = true)]
async fn main() {
// Inner service takes u32; the predicate turns a String into one.
let double = service_fn(|n: u32| async move { Ok::<_, BoxError>(n * 2) });
let mut svc = ServiceBuilder::new()
.filter(|s: String| s.trim().parse::<u32>())
.service(double);
for input in ["21", "abc"] {
match svc.ready().await.unwrap().call(input.to_string()).await {
Ok(v) => println!("filter({input:?}) = {v}"),
Err(e) => println!(
"filter({input:?}) rejected: {e} | ParseIntError={}",
e.downcast_ref::<ParseIntError>().is_some()
),
}
}
// The async predicate may await before deciding.
let echo = service_fn(|s: String| async move { Ok::<_, BoxError>(s) });
let mut svc = ServiceBuilder::new()
.filter_async(|user: String| async move {
tokio::time::sleep(Duration::from_millis(5)).await; // e.g. ask a remote ACL
if user == "mallory" {
Err::<String, BoxError>(format!("{user} is banned").into())
} else {
Ok(format!("hello, {user}"))
}
})
.service(echo);
for user in ["alice", "mallory"] {
let r = svc.ready().await.unwrap().call(user.to_string()).await;
println!("filter_async({user:?}) = {:?}", r.map_err(|e| e.to_string()));
}
}
M2 Max、rustc 1.99.0、--release,连跑 3 次输出相同:
filter("21") = 42
filter("abc") rejected: invalid digit found in string | ParseIntError=true
filter_async("alice") = Ok("hello, alice")
filter_async("mallory") = Err("mallory is banned")
谓词 s.trim().parse::<u32>() 返回 Result<u32, ParseIntError>,内层收到的是 u32。拒绝的错误被装进 BoxError,原始类型还在,downcast_ref::<ParseIntError>() 能取回来。异步谓词也把请求改写了("alice" 变成 "hello, alice")。
已知问题:AsyncFilter::layer 返回的是 FilterLayer
AsyncFilter::layer 的签名:
pub fn layer(predicate: U) -> FilterLayer<U> {
FilterLayer::new(predicate)
}
返回的是同步的 FilterLayer,它造出来的是 Filter,不是 AsyncFilter。异步谓词不满足 Predicate,造出来的服务根本不实现 Service:
use tower::filter::AsyncFilter;
use tower::{service_fn, BoxError, Layer, Service, ServiceExt};
#[tokio::main(flavor = "current_thread")]
async fn main() {
let echo = service_fn(|s: String| async move { Ok::<_, BoxError>(s) });
let layer = AsyncFilter::<(), _>::layer(|s: String| async move { Ok::<_, BoxError>(s) });
let mut svc = layer.layer(echo);
let _ = svc.ready().await.unwrap().call("hi".to_string()).await;
}
rustc 1.99.0 的报错(开头部分):
error[E0599]: the method `ready` exists for struct `Filter<ServiceFn<{closure@...}>, {closure@...}>`, but its trait bounds were not satisfied
--> examples/async_filter_layer.rs:9:15
|
9 | let _ = svc.ready().await.unwrap().call("hi".to_string()).await;
| ^^^^^ method cannot be called on `Filter<ServiceFn<{closure@...}>, {closure@...}>` due to unsatisfied trait bounds
这是 issue #597(2021 年报告,「This seems to be a copy/paste mistake」),修复的 PR #875 写这一页时还没有合并,因为改返回类型算破坏性变更。0.5.3 里要用 AsyncFilterLayer::new 或 ServiceBuilder::filter_async,两者都正确地造出 AsyncFilter。
AsyncFilter 在 tower 内部也有用户:Hedge 用它判断一个请求能不能对冲,见第 06 部分。
小结
- 谓词拿走请求,返回
Ok(新请求)或Err;关联类型Request让检查顺带改写请求、甚至换类型。闭包自动实现两个谓词 trait,错误装箱成BoxError,可以downcast回原类型。 Filter的poll_ready原样转发,call里同步检查;拒绝时内层的就绪状态留在服务身上。响应 future 是一个Either,没有额外状态。AsyncFilter把检查放进响应 future,通过后在 future 里调用内层;为此要求内层Clone,并用mem::replace把已就绪的那个换进 future。检查期间内层占的资源一直被占着。- 0.5.3 的
AsyncFilter::layer返回错了类型(#597),用AsyncFilterLayer或ServiceBuilder::filter_async。