TL;博士:
在迭代“数据块”并将它们保存到单个文件时,我无法理解 Actix Multipart;同时不会搞乱 Rust 的错误处理、高效的内存管理和异步处理。
细节和背景:
我了解一点 C++ 和基础 REST API 理论,但之前从未实施过 Web 服务。此外,我是 Rust 的新手,想使用 Actix 创建一个简单的文件服务器作为我的第一个 Rust 项目。该文件服务器将在 Kubernetes 中的简单容器中运行,可以随时添加和删除该容器的实例。文件存储在一个目录中,该目录通过挂载的卷在所有容器实例之间共享。每个实例应使用尽可能少的内存。 目标是提供...
有一些曲折,例如使用 zstd 的文件可选文件压缩、使用 xxhash128 的散列、预写日志记录或 WAL(如在 SQLite 中)等,出于简单原因,将从代码片段中删除。
我也愿意接受关于 Acitx Multipart 问题之外的进一步改进建议。
HTTP 获取: 我对它不满意,但它有效。
#[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 放置: 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()
}