use std::time::Duration;
use rainier_http::{IntoResponse, Request, Response};
use rainier_support::Error;
use crate::pipeline::{Middleware, Next};
#[derive(Debug, Clone, Copy)]
pub struct Timeout {
limit: Duration,
}
impl Timeout {
pub fn new(limit: Duration) -> Self {
Self { limit }
}
pub fn seconds(seconds: u64) -> Self {
Self::new(Duration::from_secs(seconds))
}
pub fn millis(millis: u64) -> Self {
Self::new(Duration::from_millis(millis))
}
pub fn limit(&self) -> Duration {
self.limit
}
}
#[async_trait::async_trait]
impl Middleware for Timeout {
async fn handle(&self, request: Request, next: Next) -> Response {
let method = request.method().to_string();
let uri = request.uri().path().to_string();
match tokio::time::timeout(self.limit, next.run(request)).await {
Ok(response) => response,
Err(_) => {
tracing::warn!(
%method,
%uri,
timeout_ms = self.limit.as_millis() as u64,
"request cancelled at its timeout"
);
Error::request_timeout(format!(
"The request did not finish within {} seconds.",
self.limit.as_secs_f64()
))
.into_response()
}
}
}
fn name(&self) -> &'static str {
"Timeout"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pipeline::Pipeline;
use rainier_http::{Method, StatusCode};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
fn request() -> Request {
Request::builder().method(Method::GET).uri("/slow").build()
}
#[tokio::test]
async fn a_handler_that_finishes_in_time_is_untouched() {
let response = Pipeline::new()
.through(Timeout::seconds(30))
.then(|_| async { Response::ok("in time") })
.run(request())
.await;
assert_eq!(response.status(), StatusCode::OK);
}
#[tokio::test]
async fn a_handler_that_overruns_gets_408() {
let response = Pipeline::new()
.through(Timeout::millis(10))
.then(|_| async {
tokio::time::sleep(Duration::from_secs(30)).await;
Response::ok("far too late")
})
.run(request())
.await;
assert_eq!(response.status(), StatusCode::REQUEST_TIMEOUT);
}
#[tokio::test]
async fn the_overrunning_handler_is_actually_dropped() {
let finished = Arc::new(AtomicBool::new(false));
let flag = Arc::clone(&finished);
let response = Pipeline::new()
.through(Timeout::millis(10))
.then(move |_| {
let flag = Arc::clone(&flag);
async move {
tokio::time::sleep(Duration::from_millis(500)).await;
flag.store(true, Ordering::SeqCst);
Response::ok("never")
}
})
.run(request())
.await;
assert_eq!(response.status(), StatusCode::REQUEST_TIMEOUT);
tokio::time::sleep(Duration::from_millis(700)).await;
assert!(!finished.load(Ordering::SeqCst), "the handler kept running past its timeout");
}
#[tokio::test]
async fn the_message_says_how_long_it_waited() {
let response = Pipeline::new()
.through(Timeout::millis(10))
.then(|_| async {
tokio::time::sleep(Duration::from_secs(30)).await;
Response::ok("no")
})
.run(request())
.await;
let body = response.into_string().await.unwrap();
assert!(body.contains("0.01"), "{body}");
}
}