Rust HTTP文件上传库multipart-rs的使用,multipart-rs支持高效处理multipart/form-data格式数据

Rust HTTP文件上传库multipart-rs的使用,multipart-rs支持高效处理multipart/form-data格式数据

Multipart-RS是一个简单、零分配、流式、异步的Rust multipart读写库,用于处理multipart/form-data格式数据。

读取multipart数据

以下是读取multipart数据的示例代码:

let headermap = vec![(
    "Content-Type".to_string(),
    "multipart/form-data; boundary=--974767299852498929531610575".to_string(),
)];
// 行必须以CRLF结尾
let data = b"--974767299852498929531610575\r
Content-Disposition: form-data; name=\"afile\"; filename=\"a.txt\"\r
\r
Content of a.txt.\r
--974767299852498929531610575\r
Content-Disposition: form-data; name=\"bfile\"; filename=\"b.txt\"\r
Content-Type: text/plain\r
\r
Content of b.txt.\r
--974767299852498929531610575--\r\n";

let reader = MultipartReader::from_data_with_headers(data, &headermap);

loop {
    match reader.next().await {
        Some(Ok(item)) => println!(item),
        Some(Err(e)) => panic!("Error: {:?}", e),
        None => break,
    }
}

完整示例

下面是一个完整的HTTP文件上传服务示例,展示如何使用multipart-rs处理文件上传:

use std::convert::Infallible;
use std::io::Write;
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn};
use multipart_rs::MultipartReader;

async fn handle_upload(req: Request<Body>) -> Result<Response<Body>, Infallible> {
    // 获取Content-Type头
    let content_type = req.headers()
        .get("content-type")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("");
    
    // 创建MultipartReader
    let boundary = content_type
        .split("boundary=")
        .nth(1)
        .unwrap_or("");
    
    let headermap = vec![
        ("Content-Type".to_string(), content_type.to_string())
    ];
    
    // 读取请求体
    let body = hyper::body::to_bytes(req.into_body()).await.unwrap();
    let mut reader = MultipartReader::from_data_with_headers(&body, &headermap);
    
    // 处理每个multipart部分
    while let Some(part) = reader.next().await {
        match part {
            Ok(part) => {
                // 获取文件名
                if let Some(filename) = part.headers().filename {
                    println!("Received file: {}", filename);
                    
                    // 创建文件并写入内容
                    let mut file = std::fs::File::create(filename).unwrap();
                    file.write_all(&part.data).unwrap();
                }
            }
            Err(e) => {
                eprintln!("Error processing multipart: {:?}", e);
            }
        }
    }
    
    Ok(Response::new(Body::from("Upload successful")))
}

#[tokio::main]
async fn main() {
    let make_svc = make_service_fn(|_conn| {
        async {
            Ok::<_, Infallible>(service_fn(handle_upload))
        }
    });
    
    let addr = ([127, 0, 0, 1], 3000).into();
    let server = Server::bind(&addr).serve(make_svc);
    
    println!("Server running at http://{}", addr);
    
    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}

安装

在项目目录中运行以下Cargo命令:

cargo add multipart-rs

或者在Cargo.toml中添加以下行:

multipart-rs = "0.2.1"

Multipart-RS是一个高效的multipart/form-data处理库,特别适合构建HTTP文件上传服务。它支持异步处理,能够流式读取数据而不需要一次性分配所有内存,非常适合处理大文件上传。


1 回复

Rust HTTP文件上传库multipart-rs使用指南

简介

multipart-rs是一个用于处理multipart/form-data格式数据的Rust库,特别适合HTTP文件上传场景。它提供了高效的方式来解析和生成multipart数据流,支持同步和异步操作。

主要特性

  • 支持标准multipart/form-data格式
  • 同时提供同步和异步API
  • 内存高效,可处理大文件
  • 灵活的配置选项
  • 与流行的HTTP服务器框架兼容

安装

在Cargo.toml中添加依赖:

[dependencies]
multipart = "0.18"

完整示例代码

服务器端完整示例

use multipart::server::{Multipart, SaveResult};
use std::fs::File;
use std::io::Write;
use std::path::Path;

// 同步处理上传文件
pub fn handle_upload(req: &mut impl Read) -> Result<(), Box<dyn std::error::Error>> {
    let mut multipart = Multipart::from_request(req)?;
    
    // 创建上传目录
    let upload_dir = Path::new("uploads");
    if !upload_dir.exists() {
        std::fs::create_dir(upload_dir)?;
    }

    while let Some(mut field) = multipart.read_entry()? {
        let field_name = field.headers.name.to_string();
        
        if let Some(orig_filename) = field.headers.filename {
            // 安全处理:使用UUID生成新文件名,避免路径穿越攻击
            let new_filename = format!("{}/{}", 
                upload_dir.display(),
                uuid::Uuid::new_v4().to_string()
            );
            
            println!("接收文件: {} (原始名: {})", field_name, orig_filename);
            
            let mut dest_file = File::create(&new_filename)?;
            std::io::copy(&mut field.data, &mut dest_file)?;
            println!("文件保存到: {}", new_filename);
        } else {
            // 处理文本字段
            let mut data = Vec::new();
            field.data.read_to_end(&mut data)?;
            println!("文本字段 {}: {}", field_name, String::from_utf8_lossy(&data));
        }
    }
    
    Ok(())
}

// 异步处理上传文件
#[tokio::main]
async fn async_handle_upload(req: impl AsyncRead + Unpin) -> Result<(), Box<dyn std::error::Error>> {
    let mut multipart = Multipart::from_request(req).await?;
    
    while let Some(mut field) = multipart.next_entry().await? {
        let field_name = field.headers.name.to_string();
        
        if let Some(orig_filename) = field.headers.filename {
            let new_filename = format!("uploads/{}", uuid::Uuid::new_v4().to_string());
            
            let mut file = tokio::fs::File::create(&new_filename).await?;
            while let Some(chunk) = field.data.next().await {
                file.write_all(&chunk?).await?;
            }
            println!("异步保存文件: {}", new_filename);
        } else {
            let mut content = Vec::new();
            while let Some(chunk) = field.data.next().await {
                content.extend_from_slice(&chunk?);
            }
            println!("异步文本字段: {} = {}", field_name, String::from_utf8_lossy(&content));
        }
    }
    
    Ok(())
}

客户端完整示例

use multipart::client::lazy::Multipart;
use std::fs::File;
use std::io::Read;
use reqwest::blocking::Client;

// 同步发送multipart请求
pub fn send_file_sync(username: &str, avatar_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let mut data = Vec::new();
    
    // 构建multipart数据
    Multipart::new()
        .add_text("username", username)
        .add_file("avatar", avatar_path)?
        .write_to(&mut data)?;
    
    // 使用reqwest发送请求
    let client = Client::new();
    let response = client.post("http://example.com/upload")
        .header("Content-Type", "multipart/form-data; boundary=your_boundary")
        .body(data)
        .send()?;
    
    println!("上传响应: {:?}", response);
    Ok(())
}

// 异步发送multipart请求
#[tokio::main]
async fn send_file_async(username: &str, avatar_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    use reqwest::multipart;
    use tokio::fs::File;
    use tokio::io::AsyncReadExt;
    
    // 读取文件内容
    let mut file = File::open(avatar_path).await?;
    let mut file_data = Vec::new();
    file.read_to_end(&mut file_data).await?;
    
    // 创建multipart表单
    let form = multipart::Form::new()
        .text("username", username.to_string())
        .part("avatar", 
            multipart::Part::bytes(file_data)
                .file_name("avatar.png")
                .mime_str("image/png")?
        );
    
    // 发送异步请求
    let client = reqwest::Client::new();
    let response = client.post("http://example.com/upload")
        .multipart(form)
        .send()
        .await?;
    
    println!("异步上传响应: {:?}", response);
    Ok(())
}

高级配置完整示例

use multipart::server::{SaveResult, save::SaveBuilder};
use std::path::Path;

// 带配置的文件上传处理
pub fn handle_upload_with_config(
    req: &mut impl Read,
    max_size: usize,
    max_fields: usize,
    temp_dir: &str
) -> Result<(), Box<dyn std::error::Error>> {
    // 创建临时目录
    let temp_path = Path::new(temp_dir);
    if !temp_path.exists() {
        std::fs::create_dir_all(temp_path)?;
    }

    // 配置保存选项
    let save_result = SaveBuilder::new()
        .size_limit(max_size)       // 设置最大文件大小
        .temp_dir(temp_dir)         // 设置临时目录
        .with_fields_limit(max_fields) // 设置最大字段数
        .save(req)?;
    
    match save_result {
        SaveResult::Full(entries) => {
            println!("成功接收 {} 个文本字段和 {} 个文件",
                entries.fields.len(),
                entries.files.len()
            );
            
            // 处理文本字段
            for (name, field) in entries.fields {
                println!("文本字段 {}: {:?}", name, field);
            }
            
            // 处理文件
            for (name, files) in entries.files {
                println!("文件字段 {} 有 {} 个文件", name, files.len());
                for file in files {
                    println!("保存文件: {:?}", file);
                    // 这里可以移动文件到永久存储位置
                }
            }
        }
        SaveResult::Partial(partial, error) => {
            eprintln!("部分上传完成,错误: {}", error);
            // 清理已上传的部分文件
            for (_, files) in partial.files {
                for file in files {
                    let _ = std::fs::remove_file(file.path);
                }
            }
        }
        SaveResult::Error(error) => {
            return Err(Box::new(error));
        }
    }
    
    Ok(())
}

注意事项

  1. 生产环境务必设置合理的文件大小限制和字段数量限制
  2. 处理上传文件时要注意安全,不要直接使用客户端提供的文件名
  3. 考虑使用临时文件处理大文件上传,避免内存耗尽
  4. 异步API通常能提供更好的性能,特别是在高并发场景下
  5. 文件上传目录应设置适当权限
  6. 考虑实现文件校验和病毒扫描
回到顶部