f6dd41fd41
CI / Build, lint & test (push) Successful in 2m21s
Docs: - .env.example: documents all environment variables with defaults - CONTRIBUTING.md: dev setup, running tests, code style, design constraints - MODDING.md: full schema reference for cards, packs, objectives, and SBCs - .gitea/workflows/ci.yml: Gitea Actions CI (fmt check, clippy, build, test) Middleware (#50-52): - Body limit (256 KB) via DefaultBodyLimit on all routes (#50) - Concurrency limit (256 concurrent requests) via semaphore middleware; returns 429 Too Many Requests when at capacity (#51) - Correlation IDs via tower_http request_id layers; sets x-request-id UUID on every request and propagates it to the response headers (#52) Layer order (outermost → innermost): CorsLayer → DefaultBodyLimit → concurrency limit → SetRequestId → TraceLayer → PropagateRequestId → routes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
33 lines
1.2 KiB
Rust
33 lines
1.2 KiB
Rust
use axum::{extract::Request, http::StatusCode, middleware::Next, response::Response};
|
|
use std::sync::{Arc, LazyLock};
|
|
use tokio::sync::Semaphore;
|
|
use tower_http::request_id::{MakeRequestId, RequestId};
|
|
use uuid::Uuid;
|
|
|
|
/// Maximum concurrent in-flight requests. Responds 429 when at capacity.
|
|
const MAX_CONCURRENT: usize = 256;
|
|
|
|
static SEMAPHORE: LazyLock<Arc<Semaphore>> =
|
|
LazyLock::new(|| Arc::new(Semaphore::new(MAX_CONCURRENT)));
|
|
|
|
/// Axum middleware that limits concurrent in-flight requests.
|
|
/// Returns 429 Too Many Requests when the limit is reached.
|
|
pub async fn limit_concurrency(req: Request, next: Next) -> Result<Response, StatusCode> {
|
|
let Ok(_permit) = SEMAPHORE.try_acquire() else {
|
|
return Err(StatusCode::TOO_MANY_REQUESTS);
|
|
};
|
|
Ok(next.run(req).await)
|
|
}
|
|
|
|
/// Generates a UUID v4 for each request and attaches it as `x-request-id`.
|
|
#[derive(Clone, Copy)]
|
|
pub struct MakeRequestUuid;
|
|
|
|
impl MakeRequestId for MakeRequestUuid {
|
|
fn make_request_id<B>(&mut self, _: &http::Request<B>) -> Option<RequestId> {
|
|
let id = Uuid::new_v4().to_string();
|
|
let header_val = id.parse().ok()?;
|
|
Some(RequestId::new(header_val))
|
|
}
|
|
}
|