Limit incoming requests

A body limit bounds one request's size; a rate limit bounds how many attempts reach an operation. Use both when a public endpoint can trigger expensive work. A rate limit is not authentication and does not establish who owns a resource.

Select a concrete limiter

RestApi::new() alone does not enforce a request budget. Its rate-limit switch starts enabled, but a concrete limiter must still be supplied with .rate_limit(). This distinction matters: an enabled switch is not an installed backend.

The following router function shows the complete composition for a small endpoint. It needs iet-cache-memory, iet-rate-limit, iet-http-axum, iet-core, and Axum:

rust
use axum::{routing::get, Router};
use iet_cache_memory::MemoryCache;
use iet_core::AppResult;
use iet_http_axum::RestApi;
use iet_rate_limit::{RateLimiter, RatePolicy};
use std::time::Duration;

pub fn router() -> AppResult<Router> {
    let counters = MemoryCache::<u64>::new(10_000)?;
    let policy = RatePolicy::new("public-api", 120, Duration::from_secs(60))?;
    let limiter = RateLimiter::new(counters, policy);

    Ok(RestApi::new()
        .rate_limit(limiter)
        .route("/status", get(|| async { "ok" }))
        .finish(()))
}

Select the limiter before registering routes: registration installs the middleware. Use HttpHost to serve this router; it supplies socket peer information. A custom Axum server must install ConnectInfo<SocketAddr> itself. The middleware fails closed if it cannot obtain the required identity.

Understand what is counted

The default route helper groups requests by the actual socket peer IP. It does not trust X-Forwarded-For. Behind a reverse proxy, that can mean every client shares the proxy's budget. Decide how trusted proxy identity is established before deploying; do not accept an arbitrary client-supplied forwarding header.

The cache-backed implementation uses first-hit fixed windows. Attempts are counted before downstream work, even if the handler later fails. Named policies keep unrelated budgets separate; routes sharing a policy and identity share its counter backend.

The router above was compiled against the local framework and exercised with 120 successful requests followed by a 429 response containing Retry-After. That verifies its in-process budget, not proxy behavior or multi-process limits.

MemoryCache coordinates clones inside one process. Multiple processes do not share their counters. Capacity is a bound on tracked entries, not a global number of requests, and backend failures are not treated as permission to proceed.

Verify a denial

In a test, use a fresh cache and a policy limit of two. Submit three requests with the same ConnectInfo extension. The first two should be admitted and the third should receive 429 and Retry-After. A different peer should have a separate budget. Also test the missing-identity path: otherwise an in-process test may appear to fail for the wrong reason.

Keep limiting enabled for ordinary application runs. Disabling it is appropriate only for an isolated capacity experiment that explicitly measures the unthrottled path—not as a way to make an overloaded application appear healthy.

Source: crates/features/iet-rate-limit/src/lib.rs and crates/features/iet-http-axum/src/rate_limit.rs.

IET Framework documentation — built with the IET Framework.