TL;博士:
在迭代“数据 block ”并将其保存到单个文件时,我无法理解 Actix Multipart;同时不会扰乱 Rust 错误处理、高效内存管理和异步处理。
详细信息和背景:
我了解一些 C++ 和 REST API 基础理论,但之前从未实现过 Web 服务。此外,我是 Rust 的新手,希望使用 Actix 创建一个简单的文件服务器作为我的第一个 Rust 项目。该文件服务器将在 Kubernetes 中的简单容器中运行,可以随时添加和删除该容器的实例。文件存储在单个目录中,该目录通过安装的卷在所有容器实例之间共享。每个实例应使用尽可能少的内存。 目标是提供...
- 一个简单的 HTTP GET API 端点,专注于单个文件下载的最大速度。
- 一个简单的 HTTP PUT API 端点,专注于单个文件上传的最大稳健性和安全性。
有一些变化,例如使用 zstd 进行文件可选文件压缩、使用 xxhash128 进行散列、预写式日志记录或 WAL(如 SQLite 中的)等等,出于简单原因,这些变化将从代码片段中删除。
我也欢迎进一步提出 Acitx Multipart 问题之外的改进建议。
HTTP GET: 我对此并不满意,但它确实有效。
#[get("/file/{file_id}")]
pub async fn get_file(file_id: web::Path<String>, data_path: web::Data<Config>) -> impl Responder {
let mut file_path = data_path.data_path.clone();
file_path.push('/');
file_path.push_str(&file_id);
if let Ok(mut file) = File::open(file_path) {
let mut contents = Vec::new();
if let Err(_) = file.read_to_end(&mut contents) {
return HttpResponse::InternalServerError().finish();
}
HttpResponse::Ok().body(contents)
} else {
HttpResponse::NotFound().finish()
}
}
}
HTTP PUT: while 循环中的所有内容绝对是垃圾。这就是我需要你帮助的地方。
#[put("/file/{file_id}")]
pub async fn put_file(
data_path: web::Data<Config>, mut payload: Multipart, request: HttpRequest) -> impl Responder {
// 10 MB
const MAX_FILE_SIZE: u64 = 1024 * 1024 * 10;
const MAX_FILE_COUNT: i32 = 1;
// detect malformed requests
let content_length: u64 = match request.headers().get("content-length") {
Some(header_value) => header_value.to_str().unwrap_or("0").parse().unwrap_or(0),
None => 0,
};
// reject malformed requests
match content_length {
0 => return HttpResponse::BadRequest().finish(),
length if length > MAX_FILE_SIZE => {
return HttpResponse::BadRequest()
.body(format!("The uploaded file is too large. Maximum size is {} bytes.", MAX_FILE_SIZE));
},
_ => {}
};
let file_path = data_path.data_path.clone();
let mut file_count = 0;
while let Some(mut field) = payload.try_next().await.unwrap_or(None) {
if let Some(filename) = field.content_disposition().get_filename() {
if file_count == MAX_FILE_COUNT {
return HttpResponse::BadRequest().body(format!(
"Too many files uploaded. Maximum count is {}.", MAX_FILE_COUNT
));
}
let file_path = format!("{}{}-{}", file_path, "1", sanitize_filename::sanitize(&filename));
let mut file: File = File::create(&file_path).unwrap();
while let Some(chunk) = field.try_next().await.unwrap_or(None) {
file.write_all(&chunk).map_err(|e| {
HttpResponse::InternalServerError().body(format!(
"Failed to write to file: {}", e
))
});
}
file.flush().map_err(|e| {
HttpResponse::InternalServerError().body(format!(
"Failed to flush file: {}", e
))
});
file_count += 1;
}
}
if file_count != 1 {
return HttpResponse::BadRequest().body("Exactly one file must be uploaded.");
}
HttpResponse::Ok().finish()
}
最佳答案
我使用主要的 actix-web 包解决了这个问题。
请注意,此解决方案依赖于默认的 actix multipart 行为,该行为在接收上传的文件时创建临时文件。以下是官方文档中的一个重要通知:
The default constructor, NamedTempFile::new(), creates files in the location returned by std::env::temp_dir().
我使用std::fs:rename()将此文件移动到我的目标目录。这种“临时文件”行为对我来说很有用,因为我的磁盘存储性能非常好,而内存使用是我主要关心的问题。另请记住, std::fs:rename() 的作用与移动“mv”类似。因此,请确保 std::env::temp_dir() 和您的目标目的地设置为同一文件系统上的路径,以防止完整的文件复制。
#[derive(MultipartForm)]
pub struct Upload {
file: TempFile,
}
#[put("/file")]
pub async fn put_file(
config: web::Data<Config>, form: MultipartForm<Upload>) -> impl Responder {
const MAX_FILE_SIZE: u64 = 1024 * 1024 * 10; // 10 MB
const MAX_FILE_COUNT: i32 = 1;
// reject malformed requests
match form.file.size {
0 => return HttpResponse::BadRequest().finish(),
length if length > MAX_FILE_SIZE.try_into().unwrap() => {
return HttpResponse::BadRequest()
.body(format!("The uploaded file is too large. Maximum size is {} bytes.", MAX_FILE_SIZE));
},
_ => {}
};
let temp_file_path = form.file.file.path();
let file_name: &str = form
.file
.file_name
.as_ref()
.map(|m| m.as_ref())
.unwrap_or("null");
let mut file_path = PathBuf::from(&config.data_path);
file_path.push(&sanitize_filename::sanitize(&file_name));
match std::fs::rename(temp_file_path, file_path) {
Ok(_) => HttpResponse::Ok().finish(),
Err(_) => HttpResponse::InternalServerError().finish(),
}
}
关于rust - 如何高效地使用 Actix Multipart 将单个文件上传到磁盘?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75848399/