AxumMiddleTechnical
Как обрабатывать загрузку файлов в Axum с помощью multipart?
Используйте экстрактор Multipart из axum (фича multipart в Cargo.toml); итерируйтесь через next_field().await, читайте данные методом .bytes() или чанками через .chunk(), и ограничивайте размер через DefaultBodyLimit.
Загрузка файлов в Axum через multipart
Axum поддерживает multipart-загрузку через крейт axum::extract::Multipart, который автоматически парсит тело запроса с типом multipart/form-data. Для работы необходимо подключить фичу multipart в зависимости Axum.
Подключение зависимости
# Cargo.toml
[dependencies]
axum = { version = "0.7", features = ["multipart"] }
tokio = { version = "1", features = ["full"] }
tokio-util = "0.7"
bytes = "1"
Базовый обработчик загрузки
use axum::{
extract::Multipart,
routing::post,
Router,
Json,
};
use std::path::PathBuf;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
async fn upload_handler(mut multipart: Multipart) -> Result<Json<serde_json::Value>, String> {
let mut uploaded_files: Vec<String> = Vec::new();
while let Some(field) = multipart.next_field().await.map_err(|e| e.to_string())? {
let name = field.name().unwrap_or("unknown").to_string();
let file_name = field
.file_name()
.map(|s| s.to_string())
.unwrap_or_else(|| format!("{}_upload", name));
// Считываем содержимое поля
let data = field.bytes().await.map_err(|e| e.to_string())?;
// Сохраняем файл
let path = PathBuf::from("uploads").join(&file_name);
let mut file = File::create(&path).await.map_err(|e| e.to_string())?;
file.write_all(&data).await.map_err(|e| e.to_string())?;
uploaded_files.push(file_name);
}
Ok(Json(serde_json::json!({ "uploaded": uploaded_files })))
}
#[tokio::main]
async fn main() {
tokio::fs::create_dir_all("uploads").await.unwrap();
let app = Router::new().route("/upload", post(upload_handler));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Потоковая запись для больших файлов
Метод .bytes() загружает всё в память. Для больших файлов используйте .chunk() в цикле:
use futures::TryStreamExt;
async fn upload_stream(mut multipart: Multipart) -> Result<String, String> {
while let Some(mut field) = multipart.next_field().await.map_err(|e| e.to_string())? {
let file_name = field
.file_name()
.unwrap_or("file")
.to_string();
let path = PathBuf::from("uploads").join(&file_name);
let mut file = File::create(&path).await.map_err(|e| e.to_string())?;
// Читаем чанками, не загружая всё в RAM
while let Some(chunk) = field.chunk().await.map_err(|e| e.to_string())? {
file.write_all(&chunk).await.map_err(|e| e.to_string())?;
}
}
Ok("OK".to_string())
}
Ограничение размера файла
По умолчанию Axum не ограничивает размер multipart-тела. Используйте слой DefaultBodyLimit:
use axum::extract::DefaultBodyLimit;
let app = Router::new()
.route("/upload", post(upload_handler))
.layer(DefaultBodyLimit::max(10 * 1024 * 1024)); // 10 MB
Подводные камни
- Без фичи
multipartв Cargo.toml типMultipartнедоступен — получите ошибку компиляции. - Метод
.bytes()буферизует весь файл в памяти; для файлов более 1–5 MB используйте потоковое чтение через.chunk(). - Отсутствие
DefaultBodyLimitоткрывает вектор DoS-атаки через загрузку гигантских файлов. - Имя файла из
field.file_name()приходит от клиента и не безопасно: всегда санируйте путь (проверяйте на.., абсолютные пути, недопустимые символы). - MIME-тип из
field.content_type()тоже задаётся клиентом — не доверяйте ему, проверяйте сигнатуру файла (magic bytes). - Поле может быть текстовым (не файлом) — всегда проверяйте
field.file_name()и обрабатывайте оба случая. - При параллельной загрузке нескольких файлов в одном запросе убедитесь, что генерируете уникальные имена, иначе файлы перезапишут друг друга.
- Не вызывайте
next_field()после того, как предыдущее поле не было полностью прочитано — это приводит к ошибке парсинга.
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.