文件上传 #
文件上传是Web应用中常见的功能。Rocket提供了简单而强大的文件上传处理能力。
基本配置 #
添加依赖 #
toml
[dependencies]
rocket = { version = "0.5", features = ["json"] }
单文件上传 #
使用TempFile #
rust
use rocket::form::Form;
use rocket::fs::TempFile;
#[derive(FromForm)]
struct Upload<'r> {
file: TempFile<'r>,
}
#[post("/upload", data = "<form>")]
async fn upload(mut form: Form<Upload<'_>>) -> String {
let filename = form.file.name().unwrap_or("unknown");
let size = form.file.len();
format!("Uploaded '{}' ({} bytes)", filename, size)
}
保存文件 #
rust
use rocket::form::Form;
use rocket::fs::TempFile;
use std::path::Path;
#[derive(FromForm)]
struct Upload<'r> {
file: TempFile<'r>,
}
#[post("/upload", data = "<form>")]
async fn upload(mut form: Form<Upload<'_>>) -> Result<String, String> {
let filename = form.file.name().unwrap_or("uploaded");
let dest = Path::new("uploads").join(filename);
form.file.persist_to(&dest)
.await
.map_err(|e| format!("Failed to save: {}", e))?;
Ok(format!("Saved to {:?}", dest))
}
HTML表单 #
html
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<button type="submit">Upload</button>
</form>
多文件上传 #
固定数量文件 #
rust
#[derive(FromForm)]
struct MultiUpload<'r> {
avatar: TempFile<'r>,
banner: TempFile<'r>,
}
#[post("/upload-multi", data = "<form>")]
async fn upload_multi(form: Form<MultiUpload<'_>>) -> String {
let avatar_name = form.avatar.name().unwrap_or("unknown");
let banner_name = form.banner.name().unwrap_or("unknown");
format!("Avatar: {}, Banner: {}", avatar_name, banner_name)
}
动态数量文件 #
rust
#[derive(FromForm)]
struct FilesUpload<'r> {
files: Vec<TempFile<'r>>,
}
#[post("/upload-files", data = "<form>")]
async fn upload_files(form: Form<FilesUpload<'_>>) -> String {
let count = form.files.len();
let names: Vec<_> = form.files.iter()
.filter_map(|f| f.name())
.collect();
format!("Uploaded {} files: {:?}", count, names)
}
文件信息 #
获取文件属性 #
rust
use rocket::fs::TempFile;
use rocket::http::ContentType;
#[derive(FromForm)]
struct Upload<'r> {
file: TempFile<'r>,
}
#[post("/info", data = "<form>")]
async fn file_info(form: Form<Upload<'_>>) -> String {
let info = format!(
"Name: {:?}\nSize: {} bytes\nContent-Type: {:?}",
form.file.name(),
form.file.len(),
form.file.content_type()
);
info
}
文件属性列表 #
| 属性 | 方法 | 说明 |
|---|---|---|
| 文件名 | file.name() |
原始文件名 |
| 大小 | file.len() |
文件大小(字节) |
| 内容类型 | file.content_type() |
MIME类型 |
| 路径 | file.path() |
临时文件路径 |
文件验证 #
验证文件类型 #
rust
use rocket::form::{Form, FromForm};
use rocket::fs::TempFile;
use rocket::http::ContentType;
#[derive(FromForm)]
struct ImageUpload<'r> {
#[field(validate = validate_image_type)]
image: TempFile<'r>,
}
fn validate_image_type(file: &TempFile<'_>) -> form::Result<'_, ()> {
let valid_types = ["image/jpeg", "image/png", "image/gif"];
match file.content_type() {
Some(ct) if valid_types.contains(&ct.to_string().as_str()) => Ok(()),
_ => Err(form::Error::validation("Invalid image type").into()),
}
}
#[post("/upload-image", data = "<form>")]
async fn upload_image(form: Form<ImageUpload<'_>>) -> String {
format!("Valid image: {:?}", form.image.name())
}
验证文件大小 #
rust
const MAX_SIZE: u64 = 10 * 1024 * 1024; // 10MB
#[derive(FromForm)]
struct LimitedUpload<'r> {
#[field(validate = validate_size)]
file: TempFile<'r>,
}
fn validate_size(file: &TempFile<'_>) -> form::Result<'_, ()> {
if file.len() > MAX_SIZE {
Err(form::Error::validation("File too large (max 10MB)").into())
} else {
Ok(())
}
}
文件存储 #
本地存储 #
rust
use rocket::fs::TempFile;
use std::path::PathBuf;
use uuid::Uuid;
async fn save_file(file: &mut TempFile<'_>) -> Result<PathBuf, std::io::Error> {
let upload_dir = PathBuf::from("uploads");
std::fs::create_dir_all(&upload_dir)?;
let extension = file.name()
.and_then(|n| n.rsplit('.').next())
.unwrap_or("bin");
let filename = format!("{}.{}", Uuid::new_v4(), extension);
let dest = upload_dir.join(&filename);
file.persist_to(&dest).await?;
Ok(dest)
}
#[derive(FromForm)]
struct Upload<'r> {
file: TempFile<'r>,
}
#[post("/save", data = "<form>")]
async fn save_upload(mut form: Form<Upload<'_>>) -> Result<String, String> {
match save_file(&mut form.file).await {
Ok(path) => Ok(format!("Saved to {:?}", path)),
Err(e) => Err(format!("Error: {}", e)),
}
}
按日期组织 #
rust
use chrono::Local;
async fn save_with_date(file: &mut TempFile<'_>) -> Result<PathBuf, std::io::Error> {
let now = Local::now();
let date_dir = format!("uploads/{}/{}/{}",
now.format("%Y"),
now.format("%m"),
now.format("%d")
);
std::fs::create_dir_all(&date_dir)?;
let filename = file.name().unwrap_or("uploaded");
let dest = PathBuf::from(date_dir).join(filename);
file.persist_to(&dest).await?;
Ok(dest)
}
进度跟踪 #
使用Fairing跟踪 #
rust
use rocket::{Request, Data, Response};
use rocket::fairing::{Fairing, Info, Kind};
struct UploadProgress;
#[rocket::async_trait]
impl Fairing for UploadProgress {
fn info(&self) -> Info {
Info {
name: "Upload Progress",
kind: Kind::Request,
}
}
async fn on_request(&self, request: &mut Request<'_>, _data: &mut Data<'_>) {
if let Some(len) = request.headers().get_one("Content-Length") {
println!("Upload size: {} bytes", len);
}
}
}
配置限制 #
Rocket.toml配置 #
toml
[default]
limits = { file = 10MiB, data-form = 5MiB }
[default.limits]
file = 10MiB
data-form = 5MiB
json = 1MiB
代码中配置 #
rust
use rocket::data::{Limits, ByteUnit};
#[launch]
fn rocket() -> _ {
rocket::build()
.configure(rocket::Config::figment()
.merge(("limits.file", ByteUnit::Megabyte(20)))
)
.mount("/", routes![upload])
}
完整示例 #
rust
#[macro_use] extern crate rocket;
use rocket::form::{Form, FromForm};
use rocket::fs::TempFile;
use rocket::http::ContentType;
use std::path::PathBuf;
use uuid::Uuid;
const MAX_FILE_SIZE: u64 = 10 * 1024 * 1024;
const ALLOWED_TYPES: &[&str] = &["image/jpeg", "image/png", "image/gif", "image/webp"];
#[derive(FromForm)]
struct Upload<'r> {
description: String,
#[field(validate = validate_file)]
file: TempFile<'r>,
}
fn validate_file(file: &TempFile<'_>) -> form::Result<'_, ()> {
if file.len() > MAX_FILE_SIZE {
return Err(form::Error::validation("File too large (max 10MB)").into());
}
match file.content_type() {
Some(ct) if ALLOWED_TYPES.contains(&ct.to_string().as_str()) => Ok(()),
_ => Err(form::Error::validation("Invalid file type").into()),
}
}
async fn save_upload(file: &mut TempFile<'_>) -> Result<PathBuf, std::io::Error> {
let upload_dir = PathBuf::from("uploads");
std::fs::create_dir_all(&upload_dir)?;
let extension = file.name()
.and_then(|n| n.rsplit('.').next())
.unwrap_or("bin");
let filename = format!("{}.{}", Uuid::new_v4(), extension);
let dest = upload_dir.join(filename);
file.persist_to(&dest).await?;
Ok(dest)
}
#[derive(FromForm)]
struct MultiUpload<'r> {
description: String,
files: Vec<TempFile<'r>>,
}
#[get("/")]
fn index() -> &'static str {
r#"
<h1>File Upload</h1>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="text" name="description" placeholder="Description" />
<input type="file" name="file" accept="image/*" />
<button type="submit">Upload</button>
</form>
<h2>Multiple Files</h2>
<form action="/upload-multi" method="POST" enctype="multipart/form-data">
<input type="text" name="description" placeholder="Description" />
<input type="file" name="files" multiple accept="image/*" />
<button type="submit">Upload All</button>
</form>
"#
}
#[post("/upload", data = "<form>")]
async fn upload(mut form: Form<Upload<'_>>) -> Result<String, String> {
let filename = form.file.name().unwrap_or("unknown");
let path = save_upload(&mut form.file)
.await
.map_err(|e| format!("Save error: {}", e))?;
Ok(format!(
"Saved '{}' to {:?}\nDescription: {}",
filename, path, form.description
))
}
#[post("/upload-multi", data = "<form>")]
async fn upload_multi(mut form: Form<MultiUpload<'_>>) -> String {
let mut results = Vec::new();
for file in form.files.iter_mut() {
match save_upload(file).await {
Ok(path) => results.push(format!("Saved: {:?}", path)),
Err(e) => results.push(format!("Error: {}", e)),
}
}
format!("Description: {}\n{}", form.description, results.join("\n"))
}
#[launch]
fn rocket() -> _ {
rocket::build()
.mount("/", routes![index, upload, upload_multi])
}
测试示例 #
bash
# 上传单个文件
curl -X POST http://127.0.0.1:8000/upload \
-F "description=My image" \
-F "file=@photo.jpg"
# 上传多个文件
curl -X POST http://127.0.0.1:8000/upload-multi \
-F "description=My images" \
-F "files=@photo1.jpg" \
-F "files=@photo2.jpg"
下一步 #
掌握了文件上传后,让我们继续学习 响应类型,了解如何返回各种类型的响应。
最后更新:2026-03-28