如何高效使用Actix Multipart上传单个文件到磁盘?

问题描述 投票:0回答:0

TL;博士:

在迭代“数据块”并将它们保存到单个文件时,我无法理解 Actix Multipart;同时不会搞乱 Rust 的错误处理、高效的内存管理和异步处理。

细节和背景:

我了解一点 C++ 和基础 REST API 理论,但之前从未实施过 Web 服务。此外,我是 Rust 的新手,想使用 Actix 创建一个简单的文件服务器作为我的第一个 Rust 项目。该文件服务器将在 Kubernetes 中的简单容器中运行,可以随时添加和删除该容器的实例。文件存储在一个目录中,该目录通过挂载的卷在所有容器实例之间共享。每个实例应使用尽可能少的内存。 目标是提供...

  1. 一个简单的 HTTP GET API 端点,专注于单个文件下载的最大速度。
  2. 一个简单的 HTTP PUT API 端点,专注于单个文件上传的最大健壮性和安全性。

有一些曲折,例如使用 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()
}
rust upload multipart actix-web
© www.soinside.com 2019 - 2024. All rights reserved.