Axum Multipart Upload Size Limits

Accept multipart uploads in Axum with per-field size caps, MIME allowlists, and streaming field reads that cap total request bytes.

RustFile Upload

Code

snippet
use axum::{
    extract::{DefaultBodyLimit, Multipart},
    http::StatusCode,
    routing::post,
    Router,
};
use futures_util::StreamExt;
use std::path::PathBuf;

const MAX_BODY: usize = 20 * 1024 * 1024;
const MAX_FIELD: usize = 15 * 1024 * 1024;
const ALLOWED_MIME: [&str; 2] = ["image/png", "application/pdf"];

async fn upload(mut multipart: Multipart) -> Result<(StatusCode, String), StatusCode> {
    let mut saved = Vec::new();

    while let Some(field) = multipart
        .next_field()
        .await
        .map_err(|_| StatusCode::BAD_REQUEST)?
    {
        let name = field.name().unwrap_or("file").to_string();
        let mime = field.content_type().unwrap_or("").to_string();

        if !ALLOWED_MIME.contains(&mime.as_str()) {
            return Err(StatusCode::UNSUPPORTED_MEDIA_TYPE);
        }

        let mut written: usize = 0;
        let mut buffer: Vec<u8> = Vec::with_capacity(8 * 1024);

        // 分块读取字段流:单字段上限即时检查
        while let Some(chunk) = field
            .chunk()
            .await
            .map_err(|_| StatusCode::BAD_REQUEST)?
        {
            written += chunk.len();
            if written > MAX_FIELD {
                return Err(StatusCode::PAYLOAD_TOO_LARGE);
            }
            buffer.extend_from_slice(&chunk);
        }

        let path = PathBuf::from("uploads").join(name);
        tokio::fs::write(&path, &buffer)
            .await
            .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

        saved.push(format!("{name}({written} bytes)"));
    }

    Ok((StatusCode::CREATED, saved.join(", ")))
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/upload", post(upload))
        .layer(DefaultBodyLimit::max(MAX_BODY));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}