Rust + Axum 文件上传与静态服务:用 multipart 流式落盘并托管前端

test12026-10-053 次阅读

需求拆解

一个典型的资源服务要做两件事:接收前端传来的文件,并把已上传的静态资源再吐回去。Axum 用 Multipart 提取器处理上传,用 tower_http 的 ServeDir 托管目录。关键是上传要"流式"写盘,别把大文件整块读进内存。

依赖

axum = "0.7"
tokio = { version = "1", features = ["full"] }
tower-http = { version = "0.5", features = ["fs"] }

流式上传 handler

Multipart 逐个字段遍历,每个字段用 chunk() 边读边写,避免 bytes() 一次性占内存。文件名必须过滤,否则用户可传 "../../etc/passwd" 造成路径穿越。

use axum::{
    extract::Multipart,
    http::StatusCode,
    response::IntoResponse,
    routing::post,
    Router,
};
use tokio::io::AsyncWriteExt;

async fn upload(mut multipart: Multipart) -> Result<impl IntoResponse, StatusCode> {
    while let Some(mut field) =
        multipart.next_field().await.map_err(|_| StatusCode::BAD_REQUEST)?
    {
        let name = field.name().unwrap_or("file").to_string();
        if name != "file" {
            continue;
        }
        let fname = field.file_name().unwrap_or("upload.bin").to_string();
        let path = format!("uploads/{}", sanitize(&fname));
        let mut out = tokio::fs::File::create(&path)
            .await
            .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
        while let Some(chunk) =
            field.chunk().await.map_err(|_| StatusCode::BAD_REQUEST)?
        {
            out.write_all(&chunk)
                .await
                .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
        }
        out.flush().await.ok();
    }
    Ok(StatusCode::CREATED)
}

fn sanitize(name: &str) -> String {
    name.chars()
        .filter(|c| c.is_alphanumeric() || *c == '.' || *c == '_' || *c == '-')
        .collect()
}

挂载路由与静态目录

nest_service 把 /static 路径交给 ServeDir,前端构建产物放进 public 目录后就能直接被访问。上传目录 uploads 与 public 分开,避免被静态服务直接列出。

use tower_http::services::ServeDir;

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/api/upload", post(upload))
        .nest_service("/static", ServeDir::new("public"));

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

三个工程要点

  • uploads 目录要提前创建(tokio::fs::create_dir_all),否则首个上传会 500。
  • ServeDir 默认不列目录,若想禁止访问隐藏文件可加 .append_index_html_on_directories(false) 等配置。
  • 生产环境应在反向代理(Nginx)层限制上传体大小,Axum 这边只做格式与落盘,别把限流逻辑压在应用里。
T

test1

文章作者

需求拆解 一个典型的资源服务要做两件事:接收前端传来的文件,并把已上传的静态资源再吐回去。Axum 用 Multipa...

分类
技术
发布时间
2026-10-05
字数
约 2236 字
阅读
3 次