use std::fmt::{self, Debug, Formatter};
use std::time::Duration;
use salvo_core::http::headers::{Connection, HeaderMapExt};
use salvo_core::http::{Request, Response, StatusError};
use salvo_core::{Depot, FlowCtrl, Handler, async_trait};
pub struct Timeout {
value: Duration,
error: Box<dyn Fn() -> StatusError + Send + Sync + 'static>,
}
impl Debug for Timeout {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Timeout")
.field("value", &self.value)
.finish()
}
}
impl Timeout {
#[inline]
#[must_use]
pub fn new(value: Duration) -> Self {
Self {
value,
error: Box::new(|| {
StatusError::service_unavailable()
.brief("server timed out while processing the request")
}),
}
}
#[must_use]
pub fn error(mut self, error: impl Fn() -> StatusError + Send + Sync + 'static) -> Self {
self.error = Box::new(error);
self
}
}
#[async_trait]
impl Handler for Timeout {
#[inline]
async fn handle(
&self,
req: &mut Request,
depot: &mut Depot,
res: &mut Response,
ctrl: &mut FlowCtrl,
) {
tokio::select! {
_ = ctrl.call_next(req, depot, res) => {},
_ = tokio::time::sleep(self.value) => {
res.headers_mut().typed_insert(Connection::close());
res.render((self.error)());
ctrl.skip_rest();
}
}
}
}
#[cfg(test)]
mod tests {
use salvo_core::prelude::*;
use salvo_core::test::{ResponseExt, TestClient};
use super::*;
#[tokio::test]
async fn test_timeout_handler() {
#[handler]
async fn fast() -> &'static str {
"hello"
}
#[handler]
async fn slow() -> &'static str {
tokio::time::sleep(Duration::from_secs(6)).await;
"hello"
}
let router = Router::new()
.hoop(Timeout::new(Duration::from_secs(5)))
.push(Router::with_path("slow").get(slow))
.push(Router::with_path("fast").get(fast));
let service = Service::new(router);
let content = TestClient::get("http://127.0.0.1:5801/slow")
.send(&service)
.await
.take_string()
.await
.unwrap();
assert!(content.contains("timed out while processing the request"));
let content = TestClient::get("http://127.0.0.1:5801/fast")
.send(&service)
.await
.take_string()
.await
.unwrap();
assert!(content.contains("hello"));
}
}