GridFS本身不支持Range请求,需手动实现206响应及Content-Range头;须严格解析Range头、校验边界、按chunkSize对齐读取,并正确处理start/end的包含性与越界情况。

GridFS 本身不支持 Range 请求,必须手动实现
浏览器拖动进度条、快进播放依赖服务端返回 206 Partial Content 和正确的 Content-Range 头,但 GridFS 只是把文件切块存进 fs.chunks 和 fs.files 两个集合,不提供任何 HTTP 协议层能力。你看到的“支持 Range”全是后端代码自己写的逻辑,不是驱动或 MongoDB 内置的。
常见错误是直接调用 bucket.openDownloadStream(fileId, { start, end }) 就以为搞定了——它确实能读出字节范围,但:
-
end是**不包含**的([start, end)),而 HTTPRange: bytes=1000-2000是包含末尾字节的(共 1001 字节) - 该方法不校验
start是否越界,也不设置响应头,更不会返回206 - 若
start超过file.length,它可能静默返回空流,前端就卡住或显示无限时长
解析 Range 头并校验字节边界是第一道关卡
从 req.headers.range 提取范围时,必须严格按 RFC 7233 处理:
- 格式一定是
bytes=N-M或bytes=N-,其他格式应拒掉 -
N必须是有效数字,M若存在则不能小于N - 用
file.length校验:若N >= file.length,直接返回416 Range Not Satisfiable -
M若超出file.length - 1,应自动截为file.length - 1(HTTP 是 inclusive)
漏掉校验会导致前端反复重试、播放器崩溃或显示“NaN 秒”。尤其注意:JavaScript 的 parseInt('') 返回 NaN,别直接用 Number() 而不判空。
按 chunkSize 对齐读取,避免多查或少读
GridFS 每个 chunk 默认 255KB(261120 字节),但实际值存在 fs.files.chunkSize 字段里,且可自定义。计算 chunk 索引和偏移必须用这个值,不能硬编码:
- 起始 chunk 索引:
Math.floor(start / chunkSize) - 该 chunk 内偏移:
start % chunkSize - 结束位置(含):
realEnd = Math.min(end, file.length - 1) - 末 chunk 索引:
Math.floor(realEnd / chunkSize),其读取长度为(realEnd % chunkSize) + 1
错误做法是用 for (let i = start; i 逐字节拼——性能崩盘;或忽略 chunk 边界,导致驱动内部多拉一个 chunk 再截断,白白增加 I/O 和内存开销。
手动拼接 BinData 并设置正确响应头
每个 fs.chunks.data 是 BinData 类型,不能直接 Buffer.concat() 后返回——必须精确按字节截取再合并:
- 对中间 chunk,读完整
data字段;对首 chunk,用data.subarray(offset);对末 chunk,用data.subarray(0, length) - 响应头必须设:
Content-Range: bytes ${start}-${realEnd}/${file.length}、Content-Length: ${realEnd - start + 1}、Accept-Ranges: bytes - 务必用
res.status(206),不是200;否则浏览器不认为这是分片响应,拖动会失败
最容易被忽略的是:chunk 查询必须用 find({ files_id: fileId, n: { $gte: startChunk, $lte: endChunk } }) 排序后依次读,不能用 findOne 循环查——网络往返延迟会放大数倍。生产环境建议加 { sort: { n: 1 } } 和 { projection: { data: 1, n: 1 } } 减少传输量。


















