volo_http/client/layer/
timeout.rs

1use motore::{layer::Layer, service::Service};
2use volo::context::Context;
3
4use crate::{
5    context::client::Config,
6    error::ClientError,
7    request::{Request, RequestPartsExt},
8};
9
10/// [`Layer`] for applying timeout from [`Config`].
11///
12/// This layer will be applied by default when using [`ClientBuilder::build`], without this layer,
13/// timeout from [`Client`] or [`CallOpt`] will not work.
14///
15/// [`Client`]: crate::client::Client
16/// [`ClientBuilder::build`]: crate::client::ClientBuilder::build
17/// [`CallOpt`]: crate::client::CallOpt
18#[derive(Clone, Debug, Default)]
19pub struct Timeout;
20
21impl<S> Layer<S> for Timeout {
22    type Service = TimeoutService<S>;
23
24    fn layer(self, inner: S) -> Self::Service {
25        TimeoutService { inner }
26    }
27}
28
29/// The [`Service`] generated by [`Timeout`].
30///
31/// See [`Timeout`] for more details.
32pub struct TimeoutService<S> {
33    inner: S,
34}
35
36impl<Cx, B, S> Service<Cx, Request<B>> for TimeoutService<S>
37where
38    Cx: Context<Config = Config> + Send,
39    B: Send,
40    S: Service<Cx, Request<B>, Error = ClientError> + Send + Sync,
41{
42    type Response = S::Response;
43    type Error = S::Error;
44
45    async fn call(&self, cx: &mut Cx, req: Request<B>) -> Result<Self::Response, Self::Error> {
46        let timeout = cx.rpc_info().config().timeout().cloned();
47
48        if let Some(duration) = timeout {
49            let url = req.url();
50            let fut = self.inner.call(cx, req);
51            let sleep = tokio::time::sleep(duration);
52
53            tokio::select! {
54                res = fut => res,
55                _ = sleep => {
56                    if let Some(url) = url {
57                        tracing::warn!("[Volo-HTTP] request timeout on `{url}`");
58                    }
59                    Err(crate::error::client::timeout().with_endpoint(cx.rpc_info().callee()))
60                }
61            }
62        } else {
63            self.inner.call(cx, req).await
64        }
65    }
66}