You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 01:42:02