怎么手动解析 multipart/form-data
的原始字节流
不能靠
HttpRequest.Form或
IFormCollection,就得从原始
Request.Body读字节,再按 RFC 7578 规则拆解。核心是先提取
boundary,再用它切分出每个 part。
实操建议:
从Content-Type请求头里用正则提取
boundary=...,注意可能带双引号,比如
boundary="----WebKitFormBoundaryabc123"构造分隔符行:开头两个破折号 + boundary + 可选的两个破折号(结尾 part 会多两个),即
--{boundary} 和 --{boundary}--
用 Stream.ReadAsync分块读,别一次性
ToArray()—— 大文件直接 OOM 每个 part 的头部以
\r\n\r\n结尾,前面是 headers(如
Content-Disposition、
Content-Type),后面是 body;需逐行解析 header,区分大小写不敏感
Content-Disposition
字段怎么安全提取文件名和字段名
这个 header 是识别字段类型的关键,但格式松散、编码混乱,容易误判。
常见错误现象:
直接.Split(';') 然后找 filename=,结果遇到
filename*=UTF-8''xxx.jpg就崩了 忽略引号包裹的值,把
name="file"; filename="a b.txt"错拆成
a和
b.txt没处理 RFC 5987 编码(
filename*=),中文文件名变成乱码
实操建议:
用正则匹配name="([^"]*)"和
filename="([^"]*)",优先取带引号的完整值 若遇到
filename\*=(.+?)''(.+),用
WebUtility.UrlDecode解码右侧,并按指定 charset 转字符串(默认 UTF-8) 字段名为空或只有空格?跳过,不是文件上传项
怎么区分普通字段和文件字段并分别处理
仅靠
Content-Disposition不够,必须结合是否有
filename、是否有
Content-Type、body 是否二进制来综合判断。
使用场景差异:
普通字段:无filename,body 是纯文本(可能含
\r\n),可直接用
Encoding.UTF8.GetString()文件字段:有
filename,且通常带
Content-Type(如
image/png),body 应按原始字节保存,**绝不能用字符串解码** 边界风险:part body 末尾自带
\r\n,别把它当内容写进文件
性能影响:
对小字段,内存中暂存没问题;对大文件,应边读边写入磁盘或流式处理,避免缓冲区膨胀 不要反复Seek(0)重读 stream —— 原始
RequestBody通常不可回溯,需自己缓存或用
MemoryStream中转
为什么 ReadLine()
在 multipart 解析中基本不能用
因为 HTTP body 是二进制流,不是纯文本,
ReadLine()依赖
\r\n切行,但文件内容本身可能含任意字节,包括
\r\n,会导致提前截断或解析错位。
容易踩的坑:
用StreamReader.ReadLine()解析 header —— 看似可行,但一旦某个 part 的
Content-Type是
text/plain且内容含
\r\n,后续解析全乱 假设所有换行都是
\r\n,忽略部分客户端发
\n(虽不合规,但真实存在) 没考虑 boundary 出现在文件内容里的可能性(极低,但理论上需扫描 escape)
实操建议:
header 解析用Span<byte>.IndexOf</byte>找
\r\n\r\n,而不是按行读 body 提取用
Stream.Position+
Length计算偏移,配合
boundary字节序列做滑动窗口查找 边界检测必须用字节比较(
ReadOnlySpan<byte>.SequenceEqual</byte>),别转 string 再比
最麻烦的其实是 boundary 和文件内容重叠的极端情况,实际中极少发生,但如果你的业务要存用户传的 zip 或 pdf,就得加校验逻辑——比如检查 boundary 是否出现在前 1KB 之后,否则拒绝上传。
