helloworld service
从上一篇我们知道 service_fn 最终返回的是ServiceFn 结构体,并且实现了 Service trait, 所以,如果我们不想用service_fn 来实现handler,想自己实现一个Service 结构体,那我们只要构造一个 struct来实现 Service trait就行
struct HelloWorld;
impl Service<Request<hyper::Body>> for HelloWorld {
type Response = Response<Body>;
type Error = Infallible;
type Future = Ready<Result<Self::Response, Self::Error>>; // 这里为了简单,我们直接用future::Ready<T>
// poll_ready可以用来做限流
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
// 这里我们不需要限流,直接返回Ok
std::task::Poll::Ready(Ok(()))
}
// 这里通过ready函数直接返回Ready
fn call(&mut self, req: Request<Body>) -> Self::Future {
ready(Ok(Response::new(Body::from("hello world"))))
}
}
let make_service = make_service_fn(|_conn| async {
Ok::<_, Infallible>(service_fn(handle))
});
// 将service_fn 改成 HelloWorld
let make_service = make_service_fn(|_conn| async {
Ok::<_, Infallible>(HelloWorld)
});
do some logging
接下来我们想在call 前后加先日志来统计执行的时间,很显然我们会想到在call函数前后加上日志,在这个例子中,这样做是可以的,因为这里的ready不是真正的async函数,但是假如说,我们在这里执行了一个rpc,或者http请求,或者io请求,那么这样就有问题了,因为实际上当”after async call”执行的时候,fu 还未真正开始执行,因为这里我们只是得到了一个future实例,future的poll函数并未执行,future只有在await它时才会执行
fn call(&mut self, req: Request<Body>) -> Self::Future {
info!("before async call");
let fu = ready(Ok(Response::new(Body::from("hello world"))));
info!("after async call");
fu
}
那我们要如何在call函数中执行future呢?套个async是不是就可以await了?
fn call(&mut self, req: Request<Body>) -> Self::Future {
async {
log::info!("before");
let resp = ready(Ok(Response::new(Body::from("hello world")))).await;
log::info!("after");
resp
}
}
看到ide开始飘红就感到不好了🤷🏻♀️ , cargo check看了下错误日志,因为 async block 编译后返回的是一个opaque type,不是 future::Ready类型的
error[E0308]: mismatched types
--> src/main.rs:63:9
|
62 | fn call(&mut self, req: Request<Body>) -> Self::Future {
| ------------ expected `futures::future::Ready<Result<Response<Body>, Infallible>>` because of return type
63 | / async {
64 | | log::info!("before");
65 | | let resp = ready(Ok(Response::new(Body::from("hello world")))).await;
66 | | log::info!("after");
67 | | resp
68 | | }
| |_________^ expected struct `futures::future::Ready`, found opaque type
|
::: /Users/sean/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/future/mod.rs:65:43
|
65 | pub const fn from_generator<T>(gen: T) -> impl Future<Output = T::Return>
| ------------------------------- the found opaque type
|
= note: expected struct `futures::future::Ready<Result<Response<Body>, Infallible>>`
found opaque type `impl futures::Future<Output = [async output]>`
box pin
impl Service<Request<Body>> for HelloWorld {
type Response = Response<Body>;
type Error = Infallible;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
std::task::Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
Box::pin(async move {
log::info!("before");
let resp = ready(Ok(Response::new(Body::from("hello world")))).await;
log::info!("after");
resp
})
}
}
用Box::pin 把async 包起来, 返回的是一个 Pin
impl Service<Request<Body>> for HelloWorld {
type Response = Response<Body>;
type Error = Infallible;
// 类型改为 BoxFuture
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
std::task::Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
// 用Box::pin 包装起来
Box::pin(async move {
log::info!("before");
let resp = ready(Ok(Response::new(Body::from("hello world")))).await;
log::info!("after");
resp
})
}
}
logging middleware
通常我们希望用一个middleware来做请求日志,而不是在每个service中记录日志
#[derive(Clone, Copy)]
struct Logging<S> {
inner: S,
}
impl<S> Logging<S> {
fn new(inner: S) -> Self {
Self { inner }
}
}
impl<S, B> Service<Request<B>> for Logging<S>
where
S: Service<Request<B>> + Clone + Send + 'static, // 我们限制S必须实现Service<Request> trait
B: HttpBody + Send + 'static, // B 必须实现 HttpBody
S::Future: Send,
{
type Response = S::Response;
type Error = S::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx) // 直接代理到inner的 poll_ready上
}
fn call(&mut self, req: Request<B>) -> Self::Future {
let mut inner = self.inner.clone(); // 这里要clone因为 我们约束了 Self::Future 必须是'static的
Box::pin(async move {
let method = req.method().clone();
let path = req.uri().path().to_string();
log::debug!("process req {} {}", method, path);
let response = inner.call(req).await; // 这里调用inner service call函数
// 如果我们在这里使用 self.inner.call() 则代表,Self::Future borrow 了 self,但我们要求返回的Future
// 是'static的, 表示这个Future不会borrow anything,所以这里要将inner变成一个owned value, 可以move into Future
log::debug!("finish process req {} {}", method, path);
response
})
}
}
使用logging middleware
// 将service_fn 改成 Logging::new(HelloWorld)
let make_service = make_service_fn(|_conn| async {
Ok::<_, Infallible>(Logging::new(HelloWorld))
});
这样即实现了一个logging middleware service了
小结
我们通过实现Service trait 做到了logging middleware,但是并不完美,因为用Box::pin 包装之后每次都会在堆上分配,像是logging这样的middleware每次请求都要分配一次对性能是有影响的,rust的优势也无法体现,所以我们需要实现一个lowlevel Future