1. ext 设计模式
xxx-ext 是 Rust 当中常用的一个设计模式,在满足某个 Trait 的前提下,引入很多的扩展功能(比如最常用的 Iterator,其实 skip, enumerate, take 都是针对 Iterator trait 的扩充)
Tower 也给 Service 引入了这个设计模式,十分好用。
源码如下,TL;DR
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
   | pub trait ServiceExt<Request>: tower_service::Service<Request> {     #[deprecated(since = "0.3.1", note = "prefer `ready_and` which yields the service")]     fn ready(&mut self) -> Ready<'_, Self, Request>     where         Self: Sized,     {         #[allow(deprecated)]         Ready::new(self)     }
      fn ready_and(&mut self) -> ReadyAnd<'_, Self, Request>     where         Self: Sized,     {         ReadyAnd::new(self)     }
      fn ready_oneshot(self) -> ReadyOneshot<Self, Request>     where         Self: Sized,     {         ReadyOneshot::new(self)     }
      fn oneshot(self, req: Request) -> Oneshot<Self, Request>     where         Self: Sized,     {         Oneshot::new(self, req)     }
      #[cfg(feature = "call-all")]     fn call_all<S>(self, reqs: S) -> CallAll<Self, S>     where         Self: Sized,         Self::Error: Into<Error>,         S: futures_core::Stream<Item = Request>,     {         CallAll::new(self, reqs)     }       ... }
  impl<T: ?Sized, Request> ServiceExt<Request> for T where T: tower_service::Service<Request> {}
 
  | 
 
ServiceExt 中的的几个函数,对于当前的 Service 做了不同的封装,下面会挑选几个进行细致的分析。
2. ReadyOneShot & Ready  实现
ReadyOneshot 结构中,保存了一个 Option<Service>,并且在 poll 方法中通过 ready! 匹配 poll_ready 的结果
之所以称为 ReadyOneShot,是因为 ReadyOneShort::poll 方法返回 Ready 之前会将 Option 中的 Service  取出来,如果再次 poll 这个 ReadyOneShot 结构,肯定会发生 panic(符合预期)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   |  pub struct ReadyOneshot<T, Request> {     inner: Option<T>,     _p: PhantomData<fn() -> Request>, }
 
  impl<T, Request> Unpin for ReadyOneshot<T, Request> {}
  impl<T, Request> ReadyOneshot<T, Request> where     T: Service<Request>, {     #[allow(missing_docs)]     pub fn new(service: T) -> Self {         Self {             inner: Some(service),             _p: PhantomData,         }     } }
  impl<T, Request> Future for ReadyOneshot<T, Request> where     T: Service<Request>, {     type Output = Result<T, T::Error>;
      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {         ready!(self             .inner             .as_mut()             .expect("poll after Poll::Ready")             .poll_ready(cx))?;
          Poll::Ready(Ok(self.inner.take().expect("poll after Poll::Ready")))     } }
 
  | 
 
Ready 是针对 ReadyOneshot 结构简单封装,主要是为了重复利用 Service,不需要拿到 Service 的所有权,只需 Service 的可变引用即可。Ready::poll 中所做的,也仅仅是 forward 内部 ReadyOneShot::poll 的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   |  pub struct Ready<'a, T, Request>(ReadyOneshot<&'a mut T, Request>);
  impl<'a, T, Request> Ready<'a, T, Request> where     T: Service<Request>, {     #[allow(missing_docs)]     pub fn new(service: &'a mut T) -> Self {         Self(ReadyOneshot::new(service))     } }
  impl<'a, T, Request> Future for Ready<'a, T, Request> where     T: Service<Request>, {     type Output = Result<&'a mut T, T::Error>;
      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {         Pin::new(&mut self.0).poll(cx)     } }
 
  | 
 
你可能不禁要问了,ReadyOneShot 和 Ready 有什么好处吗?我真的需要它吗?
哎,别提,你还真离不开它!
由于 Service 将 poll_ready 函数单独抽离出来,导致我们在 call 之前,一定要先等待 poll_ready 返回,但手动去匹配 Poll::Ready 和 Poll::Pending 是一件很枯燥,但又不得不去做的事情。
得益于这套扩展,我们可以非常轻松地在代码中写出下面的链式调用 service.ready().await.unwrap().call(req).await,省去了大段的 boilerplate
3. 其他
再来看一下其它的拓展功能,其实在实际中用的不算太多了,但也是很有意思的功能。
比如 call_all 函数,会接收一个 Stream of Request,返回一个 Stream of Respone,相当于 JavaScript 里面的 Promise.all
而 and_then 描述的 service 执行依赖关系,相当于 JavaScript 里面的 Promise.resolve,这个名字听上去就很 Rusty,还有更多的组合式的扩展机制,比如 map_response, map_err, map_result,这里就不一一列举了。