html表单原生提交无法流式上传gb级文件,因浏览器必须将整个文件读入内存构造完整multipart请求体,导致内存峰值与文件大小相当;即使设enctype="multipart/form-data",也仅指定协议格式而非传输模式,真正的流式上传需用fetch配合file.stream()及content-range分片。

HTML 表单原生 <form></form> 提交无法流式上传 GB 级文件,浏览器会把整个文件读进内存再发出去,必然触发卡顿、假死甚至崩溃——这不是配置问题,是浏览器实现机制决定的。
为什么 <form enctype="multipart/form-data"></form> 不能流式上传
表单提交本质是同步构造一个完整 multipart 请求体,浏览器必须先将所有文件内容拼合成一块二进制数据(含 boundary),再整体交给网络栈。这个过程绕不开内存拷贝,file.size 多大,内存峰值就接近多大。
- 选中一个 4GB 视频后,Chrome 内存占用瞬间上涨 3.8+ GB,UI 响应延迟明显
- 即使设置了
enctype="multipart/form-data",也**不等于**启用流式;它只是协议格式,不是传输模式 -
input[type="file"]的files属性只是引用,但表单 submit 时会强制触发底层 Blob 全量读取 - 服务端收到的
Content-Length是总大小,但浏览器早已在发送前完成全部内存加载
fetch + ReadableStream 才是真流式上传路径
只有显式用 fetch 发起请求,并传入 file.stream() 返回的流,才能让浏览器边读磁盘边发包,内存占用稳定在几 MB 级别。
- 必须用
file.stream(),不能用new FileReader().readAsArrayBuffer(file)——后者仍是全量加载 - 需手动设置
Content-Range头(如bytes 0-1048575/4294967296),服务端靠它定位写入位置 - Safari 和 Firefox ReadableStream 上传,降级方案只能回退到
file.slice()分片 +XMLHttpRequest - 注意:直接
fetch(url, { method: 'POST', body: file })仍会全量加载,必须走stream()路径
分片上传比流式更兼容,但要注意 chunk 边界对齐
用 file.slice(start, end) 切块是最稳妥的跨浏览器方案,但切得不准会导致服务端合并失败或校验不通过。
- 推荐 chunk size 设为 2–5 MB(如
5 * 1024 * 1024),太小增加 HTTP 开销,太大削弱断点续传价值 - start/end 必须是整数字节偏移,不能按“第 n 个 chunk”粗略算——
file.size % chunkSize !== 0时最后一块要单独处理 - 每个请求必须携带唯一
upload_id、当前chunk_index、总块数total_chunks,缺一不可 - 前端需记录已成功上传的
chunk_index数组,恢复上传时跳过已传块,而非重置计数器
真正压测内存时,别只看 JS 堆内存——浏览器的渲染进程私有内存(如 Chrome 的 Blink GC 区)才是瓶颈。分片和流式都不是银弹,关键在根据目标浏览器版本和用户网络质量做 fallback 编排,而不是幻想一个 HTML 标签能解决所有问题。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











