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(())
}
注意事项
- 生产环境务必设置合理的文件大小限制和字段数量限制
- 处理上传文件时要注意安全,不要直接使用客户端提供的文件名
- 考虑使用临时文件处理大文件上传,避免内存耗尽
- 异步API通常能提供更好的性能,特别是在高并发场景下
- 文件上传目录应设置适当权限
- 考虑实现文件校验和病毒扫描