rust
61 lines · 9 steps
Rate-limiting an expensive Axum route with tower
An Axum PDF-rendering endpoint bounds concurrency and enforces timeouts by layering tower middleware onto a sub-router.
Explained by
highlit
1use std::time::Duration;
2
3use axum::{
4 extract::State,
5 http::StatusCode,
6 response::{IntoResponse, Json},
7 routing::post,
8 Router,
9};
10use serde::{Deserialize, Serialize};
11use tower::limit::ConcurrencyLimitLayer;
12
13#[derive(Clone)]
14struct AppState {
15 renderer: RenderClient,
16}
17
18#[derive(Deserialize)]
19struct RenderRequest {
20 template: String,
21 #[serde(default)]
22 payload: serde_json::Value,
23}
24
25#[derive(Serialize)]
26struct RenderResponse {
27 document_id: String,
28 bytes: usize,
29}
30
31async fn render_pdf(
32 State(state): State<AppState>,
33 Json(req): Json<RenderRequest>,
34) -> Result<Json<RenderResponse>, StatusCode> {
35 let rendered = state
36 .renderer
37 .render(&req.template, req.payload)
38 .await
39 .map_err(|_| StatusCode::BAD_GATEWAY)?;
40
41 Ok(Json(RenderResponse {
42 document_id: rendered.id,
43 bytes: rendered.body.len(),
44 }))
45}
46
47async fn health() -> impl IntoResponse {
48 (StatusCode::OK, "ok")
49}
50
51pub fn build_router(state: AppState) -> Router {
52 let expensive = Router::new()
53 .route("/render/pdf", post(render_pdf))
54 .layer(ConcurrencyLimitLayer::new(8))
55 .layer(tower_http::timeout::TimeoutLayer::new(Duration::from_secs(30)));
56
57 Router::new()
58 .route("/health", axum::routing::get(health))
59 .merge(expensive)
60 .with_state(state)
61}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Layers applied to a sub-router protect only its routes, so cheap endpoints like health checks stay unthrottled.
- 2tower middleware such as concurrency limits and timeouts composes onto Axum routers without touching handler logic.
- 3Axum extractors turn shared state and request bodies into typed handler arguments while the return type maps errors to HTTP responses.
Related explainers
rust
use std::cmp::Ordering; pub struct PrefixIndex { entries: Vec<String>,
Prefix search with binary partitioning in Rust
binary-search
sorting
case-insensitive
Intermediate
7 steps
javascript
const express = require('express'); const router = express.Router(); const { pool } = require('../db'); const redis = require('../redis');
Building a health check endpoint in Express
health-check
timeouts
promise-race
Intermediate
9 steps
rust
use std::cmp::Ordering; use std::str::FromStr; #[derive(Debug, Clone, PartialEq, Eq)]
Parsing and ordering semantic versions in Rust
parsing
trait-implementation
ordering
Intermediate
8 steps
php
<?php namespace App\Http\Middleware;
Idempotent requests with Laravel middleware
idempotency
middleware
caching
Advanced
8 steps
go
package middleware import ( "net/http"
A maintenance-mode gate in Gin
middleware
atomic-flag
concurrency
Intermediate
8 steps
rust
use axum::{ extract::State, routing::{get, post, MethodRouter}, Json, Router,
Self-documenting routes in Axum
builder-pattern
closures
shared-state
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/rate-limiting-an-expensive-axum-route-with-tower-explained-rust-fecb/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.