rust 41 lines · 7 steps

Turning Axum timeouts into 504 JSON

An Axum route caps a slow report handler with a Tower timeout layer and rewrites the timeout into a clean gateway-timeout response.

Explained by highlit
1use std::time::Duration;
2 
3use axum::{
4 body::Body,
5 http::{Request, Response, StatusCode},
6 response::IntoResponse,
7 routing::get,
8 Json, Router,
9};
10use serde_json::json;
11use tower::ServiceBuilder;
12use tower_http::timeout::TimeoutLayer;
13 
14pub fn report_routes() -> Router {
15 Router::new()
16 .route("/reports/heavy", get(generate_heavy_report))
17 .layer(
18 ServiceBuilder::new()
19 .layer(TimeoutLayer::new(Duration::from_secs(5)))
20 .map_response(map_timeout_to_504),
21 )
22}
23 
24fn map_timeout_to_504(response: Response<Body>) -> Response<Body> {
25 if response.status() == StatusCode::REQUEST_TIMEOUT {
26 return (
27 StatusCode::GATEWAY_TIMEOUT,
28 Json(json!({
29 "error": "report_generation_timed_out",
30 "message": "The report took too long to generate. Try narrowing the date range."
31 })),
32 )
33 .into_response();
34 }
35 response
36}
37 
38async fn generate_heavy_report(_req: Request<Body>) -> impl IntoResponse {
39 let rows = crate::reporting::aggregate_yearly_totals().await;
40 Json(json!({ "rows": rows }))
41}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Tower layers wrap handlers uniformly, so cross-cutting concerns like timeouts live outside the business logic.
  2. 2Mapping a raw framework status into a domain-specific JSON error gives clients a clearer, actionable response.
  3. 3Composing behavior with ServiceBuilder keeps ordering explicit: apply the timeout, then reshape whatever it produces.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Turning Axum timeouts into 504 JSON — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code