Cookbook / Middleware and state
Hardening an API
Security headers, CORS, rate limiting, compression and request limits in one setup.
Axeon is secure by default (size limits, timeouts, slow-client protection, no internal errors sent to clients). These built-in middleware cover the rest of a typical public API.
use axeon::middleware::{Compression, Cors, RateLimiter, SecurityHeaders};
use axeon::{Json, Server};
use axeon::json;
use std::time::Duration;
fn main() {
let mut app = Server::new();
app.max_body_size(1024 * 1024) // 413 above 1 MiB (default 2 MiB)
.max_header_size(16 * 1024) // 431 above 16 KiB
.request_timeout(Duration::from_secs(10)) // headers, body and TLS handshake
.write_timeout(Duration::from_secs(10)); // clients that stop reading
app.middleware(SecurityHeaders::new()) // HSTS, nosniff, frame options, CSP...
.middleware(Compression::new()) // gzip / deflate
.middleware(
Cors::new()
.allow_origin("https://app.example.com")
.allow_credentials(true),
)
// 120 requests a minute per client, with bursts of 20.
// Add .trust_proxy(true) only behind a proxy that sets X-Forwarded-For.
.middleware(RateLimiter::per_minute(120).burst(20));
app.get("/api/ping", |_req| async { Json(json!({ "pong": true })) });
// A tighter limit on one route.
app.route("/api/avatar")
.body_limit(64 * 1024)
.post(|_req| async { (201, "stored") });
app.listen("127.0.0.1:3000").expect("server failed to start");
}
curl -i http://127.0.0.1:3000/api/ping -H 'origin: https://app.example.com'
Notes#
- Order matters. Middleware added first runs first, so put things that must see every request (request IDs, logging, security headers) at the top.
- When the rate limit is exceeded the client gets
429with aRetry-Afterheader. - Rate limiting is per process. Behind several instances, also limit at your load balancer.
- The limit key defaults to the client IP. It can also be an API key or user.