helloworld service

从上一篇我们知道 service_fn 最终返回的是ServiceFn 结构体,并且实现了 Service trait, 所以,如果我们不想用service_fn 来实现handler,想自己实现一个Service 结构体,那我们只要构造一个 struct来实现 Service trait就行

  1. struct HelloWorld;
  2. impl Service<Request<hyper::Body>> for HelloWorld {
  3. type Response = Response<Body>;
  4. type Error = Infallible;
  5. type Future = Ready<Result<Self::Response, Self::Error>>; // 这里为了简单,我们直接用future::Ready<T>
  6. // poll_ready可以用来做限流
  7. fn poll_ready(
  8. &mut self,
  9. cx: &mut std::task::Context<'_>,
  10. ) -> std::task::Poll<Result<(), Self::Error>> {
  11. // 这里我们不需要限流,直接返回Ok
  12. std::task::Poll::Ready(Ok(()))
  13. }
  14. // 这里通过ready函数直接返回Ready
  15. fn call(&mut self, req: Request<Body>) -> Self::Future {
  16. ready(Ok(Response::new(Body::from("hello world"))))
  17. }
  18. }
  19. let make_service = make_service_fn(|_conn| async {
  20. Ok::<_, Infallible>(service_fn(handle))
  21. });
  22. // 将service_fn 改成 HelloWorld
  23. let make_service = make_service_fn(|_conn| async {
  24. Ok::<_, Infallible>(HelloWorld)
  25. });

do some logging

接下来我们想在call 前后加先日志来统计执行的时间,很显然我们会想到在call函数前后加上日志,在这个例子中,这样做是可以的,因为这里的ready不是真正的async函数,但是假如说,我们在这里执行了一个rpc,或者http请求,或者io请求,那么这样就有问题了,因为实际上当”after async call”执行的时候,fu 还未真正开始执行,因为这里我们只是得到了一个future实例,future的poll函数并未执行,future只有在await它时才会执行

  1. fn call(&mut self, req: Request<Body>) -> Self::Future {
  2. info!("before async call");
  3. let fu = ready(Ok(Response::new(Body::from("hello world"))));
  4. info!("after async call");
  5. fu
  6. }

那我们要如何在call函数中执行future呢?套个async是不是就可以await了?

  1. fn call(&mut self, req: Request<Body>) -> Self::Future {
  2. async {
  3. log::info!("before");
  4. let resp = ready(Ok(Response::new(Body::from("hello world")))).await;
  5. log::info!("after");
  6. resp
  7. }
  8. }

image.png
看到ide开始飘红就感到不好了🤷🏻‍♀️ , cargo check看了下错误日志,因为 async block 编译后返回的是一个opaque type,不是 future::Ready类型的

  1. error[E0308]: mismatched types
  2. --> src/main.rs:63:9
  3. |
  4. 62 | fn call(&mut self, req: Request<Body>) -> Self::Future {
  5. | ------------ expected `futures::future::Ready<Result<Response<Body>, Infallible>>` because of return type
  6. 63 | / async {
  7. 64 | | log::info!("before");
  8. 65 | | let resp = ready(Ok(Response::new(Body::from("hello world")))).await;
  9. 66 | | log::info!("after");
  10. 67 | | resp
  11. 68 | | }
  12. | |_________^ expected struct `futures::future::Ready`, found opaque type
  13. |
  14. ::: /Users/sean/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/future/mod.rs:65:43
  15. |
  16. 65 | pub const fn from_generator<T>(gen: T) -> impl Future<Output = T::Return>
  17. | ------------------------------- the found opaque type
  18. |
  19. = note: expected struct `futures::future::Ready<Result<Response<Body>, Infallible>>`
  20. found opaque type `impl futures::Future<Output = [async output]>`

box pin

  1. impl Service<Request<Body>> for HelloWorld {
  2. type Response = Response<Body>;
  3. type Error = Infallible;
  4. type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
  5. fn poll_ready(
  6. &mut self,
  7. cx: &mut std::task::Context<'_>,
  8. ) -> std::task::Poll<Result<(), Self::Error>> {
  9. std::task::Poll::Ready(Ok(()))
  10. }
  11. fn call(&mut self, req: Request<Body>) -> Self::Future {
  12. Box::pin(async move {
  13. log::info!("before");
  14. let resp = ready(Ok(Response::new(Body::from("hello world")))).await;
  15. log::info!("after");
  16. resp
  17. })
  18. }
  19. }

用Box::pin 把async 包起来, 返回的是一个 Pin> 类型,这样是可以work的,但有个缺陷,就是每次await的时候都会分配到堆上,如果是热路径的化,对性能会有影响,例如一个middleware,包装其他的service,如果每次调用service都要在堆上分配,对性能有较大的影响

  1. impl Service<Request<Body>> for HelloWorld {
  2. type Response = Response<Body>;
  3. type Error = Infallible;
  4. // 类型改为 BoxFuture
  5. type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
  6. fn poll_ready(
  7. &mut self,
  8. cx: &mut std::task::Context<'_>,
  9. ) -> std::task::Poll<Result<(), Self::Error>> {
  10. std::task::Poll::Ready(Ok(()))
  11. }
  12. fn call(&mut self, req: Request<Body>) -> Self::Future {
  13. // 用Box::pin 包装起来
  14. Box::pin(async move {
  15. log::info!("before");
  16. let resp = ready(Ok(Response::new(Body::from("hello world")))).await;
  17. log::info!("after");
  18. resp
  19. })
  20. }
  21. }

logging middleware

通常我们希望用一个middleware来做请求日志,而不是在每个service中记录日志

  1. #[derive(Clone, Copy)]
  2. struct Logging<S> {
  3. inner: S,
  4. }
  5. impl<S> Logging<S> {
  6. fn new(inner: S) -> Self {
  7. Self { inner }
  8. }
  9. }
  10. impl<S, B> Service<Request<B>> for Logging<S>
  11. where
  12. S: Service<Request<B>> + Clone + Send + 'static, // 我们限制S必须实现Service<Request> trait
  13. B: HttpBody + Send + 'static, // B 必须实现 HttpBody
  14. S::Future: Send,
  15. {
  16. type Response = S::Response;
  17. type Error = S::Error;
  18. type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
  19. fn poll_ready(
  20. &mut self,
  21. cx: &mut std::task::Context<'_>,
  22. ) -> std::task::Poll<Result<(), Self::Error>> {
  23. self.inner.poll_ready(cx) // 直接代理到inner的 poll_ready上
  24. }
  25. fn call(&mut self, req: Request<B>) -> Self::Future {
  26. let mut inner = self.inner.clone(); // 这里要clone因为 我们约束了 Self::Future 必须是'static的
  27. Box::pin(async move {
  28. let method = req.method().clone();
  29. let path = req.uri().path().to_string();
  30. log::debug!("process req {} {}", method, path);
  31. let response = inner.call(req).await; // 这里调用inner service call函数
  32. // 如果我们在这里使用 self.inner.call() 则代表,Self::Future borrow 了 self,但我们要求返回的Future
  33. // 是'static的, 表示这个Future不会borrow anything,所以这里要将inner变成一个owned value, 可以move into Future
  34. log::debug!("finish process req {} {}", method, path);
  35. response
  36. })
  37. }
  38. }

使用logging middleware

  1. // 将service_fn 改成 Logging::new(HelloWorld)
  2. let make_service = make_service_fn(|_conn| async {
  3. Ok::<_, Infallible>(Logging::new(HelloWorld))
  4. });

这样即实现了一个logging middleware service了

小结

我们通过实现Service trait 做到了logging middleware,但是并不完美,因为用Box::pin 包装之后每次都会在堆上分配,像是logging这样的middleware每次请求都要分配一次对性能是有影响的,rust的优势也无法体现,所以我们需要实现一个lowlevel Future