如何在Rust中不使用外部crate、在TCP层解析multipart/form-data数据
无外部依赖解析multipart/form-data实现方案
核心逻辑
- 先拆分HTTP请求的头部与载荷:两者以固定序列
\r\n\r\n作为分隔标记 - 从
Content-Type请求头中提取multipart格式的边界值(boundary),该值是客户端生成的随机串,用于分隔不同的上传字段/文件 - 用boundary拆分整个请求体,过滤掉开头的空前缀和结尾的
--结束标记 - 对每个拆分后的part,拆分part头部和内容,提取文件名、字段名等元数据后即可拿到文件的原始二进制内容
完整修改后代码
use std::io::{Read, Write}; use std::net::TcpListener; use std::net::TcpStream; fn main() { let listener = TcpListener::bind("127.0.0.1:1234").unwrap(); println!("服务启动,监听1234端口"); for stream in listener.incoming() { let stream = stream.unwrap(); handle_connection(stream); } } fn handle_connection(mut stream: TcpStream) { let mut buffer = [0; 1048576]; // 1M缓冲区,可按需调整 let n = stream.read(&mut buffer).unwrap(); // 实际读取的字节数 let req_data = &buffer[..n]; // 校验是POST请求到根路径 if !req_data.starts_with(b"POST / HTTP/1.1\r\n") { let response = "HTTP/1.1 404 NOT FOUND\r\n\r\n"; stream.write(response.as_bytes()).unwrap(); stream.flush().unwrap(); return; } // 拆分请求头和请求体 let header_body_sep = b"\r\n\r\n"; let sep_pos = req_data.windows(header_body_sep.len()).position(|w| w == header_body_sep).unwrap(); let headers = &req_data[..sep_pos]; let body = &req_data[sep_pos + header_body_sep.len()..]; // 从Content-Type头提取boundary let content_type_prefix = b"Content-Type: multipart/form-data; boundary="; let boundary_pos = headers.windows(content_type_prefix.len()).position(|w| w == content_type_prefix).unwrap(); let boundary_start = boundary_pos + content_type_prefix.len(); let boundary_end = headers[boundary_start..].windows(2).position(|w| w == b"\r\n").unwrap() + boundary_start; let boundary = &headers[boundary_start..boundary_end]; // 构造part分隔符,boundary前面要加-- let part_sep = format!("--{}", String::from_utf8_lossy(boundary)).into_bytes(); // 拆分所有part let parts: Vec<&[u8]> = body.split(|&b| b == b'\n') .map(|line| line.strip_suffix(b"\r").unwrap_or(line)) .scan(Vec::new(), |state, line| { if line == part_sep || line == format!("--{}--", String::from_utf8_lossy(boundary)).as_bytes() { if !state.is_empty() { let part = state.clone(); state.clear(); return Some(Some(part)); } return Some(None); } if !state.is_empty() { state.push(b'\n'); } state.extend_from_slice(line); Some(None) }) .flatten() .collect(); // 处理第一个part的文件内容,多文件可遍历parts let file_content = if let Some(part) = parts.first() { // 拆分part头和内容 let part_sep_pos = part.windows(header_body_sep.len()).position(|w| w == header_body_sep).unwrap(); &part[part_sep_pos + header_body_sep.len()..] } else { &[] }; // 此处file_content就是上传文件的原始二进制数据,可以直接存到变量或写入文件 println!("上传文件大小:{}字节", file_content.len()); // 返回响应 let response = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK"; stream.write(response.as_bytes()).unwrap(); stream.flush().unwrap(); }
注意事项
- 示例默认文件大小不超过1M,若需要处理更大文件,可调整缓冲区大小,或改成循环读取TCP流、动态扩容缓冲区的逻辑
- 示例只处理了单文件上传场景,多文件上传遍历所有
parts元素处理即可 - 生产环境使用请替换所有
unwrap为正式的错误处理逻辑,避免边界场景下服务panic - 如果需要保存文件到磁盘,直接把
file_content写入对应路径的文件即可
内容的提问来源于stack exchange,提问作者wastrrm
相关产品推荐
相关产品推荐

