ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Node.js文件流与断点续传实战:Range请求头与Content-Range处理

2026/9/15 23:47:21 拓冰建站 浏览量
Node.js文件流与断点续传实战:Range请求头与Content-Range处理 Node.js文件流与断点续传实战Range请求头与Content-Range处理在开发支持音视频流媒体播放、大型离线知识库下载或周报历史归档包下载的服务端时HTTP 分段请求与断点续传Range Requests Resume Downloads是支撑高可用大文件传输的核心网络协议。如果服务端对所有静态下载请求都无脑返回200 OK并一次性发送全部文件内容用户使用 Safari 或 Chrome 播放一段 500MB 的技术分享视频时浏览器无法通过拖拽进度条跳转播放Seek必须等待整个 500MB 视频下载完毕后才能快进用户下载一个 2GB 的数据包在下载到 90% 时突然网络闪断由于服务端不支持206 Partial Content下载器只能从 0% 重新全量下载极度浪费用户流量与服务器带宽。本文详解 HTTPRange协议规范并在 Node.js (Fastify / Express) 中纯手写实现支持多线程并发分段下载与精准断点续传的流式静态服务器。HTTP Range 协议通信时序客户端 (浏览器 / 下载器) Node.js 服务端 │ │ │── 1. GET /video.mp4 (带请求头: Range: bytes1000-4999) ─►│ │ │── 解析目标字节范围: start1000, end4999 │ │── 计算分段长度: chunkSize 4000 字节 │◄── 2. HTTP 206 Partial Content ───────────────│ │ 响应头: │ │ - Accept-Ranges: bytes │ │ - Content-Range: bytes 1000-4999/500000000 │ │ - Content-Length: 4000 │ │ - Content-Type: video/mp4 │ │ (仅通过 fs.createReadStream 传输指定分片) │服务端实现支持 Range 分段的流式下载控制器TypeScript// src/routes/streamDownloadRoutes.ts import { FastifyInstance, FastifyRequest, FastifyReply } from fastify; import fs from fs; import path from path; const STORAGE_ROOT path.resolve(/data/storage); export async function registerStreamDownload(app: FastifyInstance) { app.get(/api/download/file/:fileName, async (req: FastifyRequest, reply: FastifyReply) { const { fileName } req.params as { fileName: string }; const filePath path.join(STORAGE_ROOT, path.basename(fileName)); // 1. 安全检查防止路径穿越攻击 if (!fs.existsSync(filePath)) { return reply.status(404).send({ error: 请求的文件不存在 }); } const stat fs.statSync(filePath); const fileSize stat.size; const rangeHeader req.headers.range; // 2. 判定是否携带了 Range 分段请求头 if (!rangeHeader) { // 普通全量下载 (HTTP 200) reply.raw.writeHead(200, { Content-Length: fileSize, Content-Type: application/octet-stream, Accept-Ranges: bytes // 告知客户端本服务器支持 Range 分段 }); fs.createReadStream(filePath).pipe(reply.raw); return; } // 3. 解析 Range 头部规范例如 bytes0-1048575 或 bytes2048- const parts rangeHeader.replace(/bytes/, ).split(-); const start parseInt(parts[0], 10); const end parts[1] ? parseInt(parts[1], 10) : fileSize - 1; // 边界合法性校验 if (isNaN(start) || isNaN(end) || start fileSize || end fileSize || start end) { reply.header(Content-Range, bytes */${fileSize}); return reply.status(416).send(Requested Range Not Satisfiable); } const chunkSize end - start 1; // 4. 构造 HTTP 206 局部响应头 reply.raw.writeHead(206, { Content-Range: bytes ${start}-${end}/${fileSize}, Accept-Ranges: bytes, Content-Length: chunkSize, Content-Type: video/mp4 // 或根据文件后缀动态识别 MIME 类型 }); // 5. 核心通过精确字节范围创建轻量读流并管道输出 const fileStream fs.createReadStream(filePath, { start, end }); fileStream.pipe(reply.raw); }); }核心细节一支持多线程并发分块加速下载IDM / Aria2现代下载器如 IDM、Aria2在检测到服务端返回了Accept-Ranges: bytes响应头后会自动将一个 1GB 的文件划分为 8 个连续分片同时建立 8 条独立的 HTTP TCP 连接分别请求bytes0-125000000,bytes125000001-250000000等分段跑满用户本地带宽上限将大文件下载速度提升5 到 8 倍核心细节二视频 Seek 拖拽播放与流式传输当用户在 HTML5video播放器中将进度条拖动到 01:30:00 处时浏览器会向服务端发送包含对应时间点偏移量字节的Range: bytes35682910-请求服务端毫秒级响应 206 并在指定字节开始流式吐出数据视频实现“0 缓冲等待、随拖随播”的丝滑观影体验。生产环境收益彻底支持断点续传弱网下载中断后客户端只需记录已下载字节并在下次请求时附加Range: bytes${downloaded}-无需重新传输已下载部分极低的 Node.js 内存驻留依靠底层的fs.createReadStream精准按需读取指定字节区间避免了任何大文件在内存中的堆积单台服务器可轻松承载数百路并发视频点播流。