Node.js HTTP服务器加载缓慢求助:video.js视频流场景
让我帮你排查一下这个视频流服务器加载慢的问题,先揪出代码里的几个关键坑,再给你对应的修复方案:
1. 语法错误直接导致服务器异常
你在设置响应头的时候犯了个低级错误:
'Content-Type' = mime.getType(filename),
这里应该用冒号:而非等号=,而且结尾的逗号是中文的,这会直接让服务器处理请求时抛出语法错误,请求直接卡住,这绝对是加载慢的元凶之一!
2. HTML页面Gzip处理的Content-Length错误
你给HTML页面设置的Content-Length是原始未压缩页面的字节长度,但实际返回的是gzip压缩后的内容,这会让浏览器接收数据时出现校验异常,导致解析延迟甚至失败。正确的做法是要么去掉手动设置的Content-Length让Node.js自动处理,要么用压缩后的数据长度:
zlib.gzip(page, (err, data) => { if (err) { console.error(err); // 这里log.e是未定义的,换成console.error throw err; } headers['Content-Length'] = data.length; // 改用压缩后的内容长度 res.writeHead(200, headers); res.end(data); });
3. 视频文件MIME类型不匹配
你的视频是.mkv格式,但<source>里写的type="video/webm",这完全不对!MKV的正确MIME类型是video/x-matroska,浏览器识别不了正确类型,就无法高效处理流式加载,自然会卡顿。赶紧修改HTML里的source标签:
<source src="video.mkv" type="video/x-matroska"/>
另外更推荐把视频转成MP4(H.264编码)格式,它的浏览器兼容性更好,流式播放支持也更成熟。
4. 异步文件检查与同步stat的冲突
你用了异步的fs.exists,但后面立刻用同步的fs.statSync,如果文件在这两个操作之间被删除或修改,会直接抛出错误导致服务器崩溃。应该用异步的fs.stat来替代,既避免阻塞,又能统一处理文件存在性和状态:
fs.stat(filename, (err, stat) => { if (err) { if (err.code === 'ENOENT') { res.writeHead(404); return res.end(); } res.writeHead(500); return res.end(); } // 后续处理逻辑全部放在这里 });
5. 缺少缓存头优化
静态资源(JS、CSS、视频)没有设置缓存策略,浏览器每次请求都要重新下载,大大增加加载时间。给静态资源加上缓存头:
// 针对静态资源设置1天的缓存 if (/\.(js|css|ico|mkv|vtt)$/.test(filename.toLowerCase())) { headers['Cache-Control'] = 'public, max-age=86400'; }
6. 错误处理不完善
代码里的log.e(err)是未定义的,换成console.error(err);另外流的错误处理要确保响应能正确结束,避免请求挂起:
stream.on('error', err => { console.error(err); if (!res.headersSent) { res.writeHead(500); } res.end(); });
修正后的完整代码
const http = require('http'); const fs = require('fs'); const url = require('url'); const path = require('path'); const zlib = require('zlib'); const mime = require('mime'); http.createServer((req, res) => { let uri = url.parse(req.url).pathname; // 根路径返回首页 if (uri === '/') { let page = `<!DOCTYPE html> <html lang="en"> <head> <title>Video</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0"> <link rel="shortcut icon" href="favicon.ico"> <link rel="stylesheet" type="text/css" href="assets/css/video-js.min.css"> <script src="assets/js/video.min.js"></script> </head> <body> <div id="video_area"> <video id="video" class="video-js vjs-big-play-centered"> <source src="video.mkv" type="video/x-matroska"/> <track kind="subtitles" lang="en" src="en.vtt" default/> </video> </div> <script> window.player = videojs('video', { controls: true, autoplay: false, preload: 'metadata', // 改成metadata比auto更适合慢加载场景,仅加载视频元数据 fluid: true, playbackRates: [0.5, 1, 1.5, 2] }); </script> </body> </html>`; let headers = { 'Content-Encoding': 'gzip', 'Content-Type': 'text/html', 'Vary': 'Accept-Encoding' }; zlib.gzip(page, (err, data) => { if (err) { console.error(err); res.writeHead(500); return res.end(); } headers['Content-Length'] = data.length; res.writeHead(200, headers); res.end(data); }); } else { let filename = path.join("./", uri); // 用异步stat替代exists+statSync,避免阻塞和异常 fs.stat(filename, (err, stat) => { if (err) { if (err.code === 'ENOENT') { res.writeHead(404); return res.end(); } console.error(err); res.writeHead(500); return res.end(); } let headers = { 'Accept-Ranges': 'bytes', 'Content-Type': mime.getType(filename), // 修复语法错误 'Vary': 'Accept-Encoding' }; // 添加静态资源缓存头 if (/\.(js|css|ico|mkv|vtt)$/.test(filename.toLowerCase())) { headers['Cache-Control'] = 'public, max-age=86400'; } // 处理断点续传 let range = req.headers.range; let stream; if (range) { let parts = range.replace(/bytes=/, '').split('-'); let start = parseInt(parts[0]); let end = parts[1] ? parseInt(parts[1]) : stat.size - 1; // 修正Range的合法性,避免超出文件范围 start = Math.max(0, Math.min(start, stat.size - 1)); end = Math.max(start, Math.min(end, stat.size - 1)); let chunk_size = end - start + 1; stream = fs.createReadStream(filename, {start, end}); headers['Content-Length'] = chunk_size; headers['Content-Range'] = `bytes ${start}-${end}/${stat.size}`; res.writeHead(206, headers); } else { headers['Content-Length'] = stat.size; stream = fs.createReadStream(filename); res.writeHead(200, headers); } stream.on('error', err => { console.error(err); if (!res.headersSent) { res.writeHead(500); } res.end(); }); // JS/CSS文件启用gzip压缩 if (/\.(js|css)$/.test(filename.toLowerCase())) { let gzip = zlib.createGzip(); gzip.on('error', err => { console.error(err); if (!res.headersSent) { res.writeHead(500); } res.end(); }); headers['Content-Encoding'] = 'gzip'; // 压缩后删除Content-Length,让Node.js自动处理 delete headers['Content-Length']; res.writeHead(range ? 206 : 200, headers); stream.pipe(gzip).pipe(res); } else { stream.pipe(res); } }); } }).listen(80);
额外优化建议
- 推荐用
express框架配合compression、serve-static中间件搭建服务器,这些成熟库已经处理了缓存、压缩、错误处理等细节,比手写原生HTTP服务器更稳定高效。 - 如果视频文件很大,建议完善断点续传的合法性校验,避免无效请求占用资源。
- 优先把视频转成H.264编码的MP4格式,它的流式播放支持更好,浏览器兼容性更广,能有效提升加载速度。
内容的提问来源于stack exchange,提问作者askar882

