一次 egg-static Range 请求头报错的处理之旅
2024-02-02 12:44:26
一次 egg-static Range 请求头报错的处理之旅
背景
由于团队里有用到 eggjs 做后端服务,之前一直用 egg-static 作为图片的静态资源服务,用户上传图片到 eggjs 的静态资源目录后,就可以通过 url 访问,非常方便。最近接到一个新需求,需要上传视频文件到静态资源目录,同时还要求支持视频的 Range 请求,以便实现视频的断点续传功能。
问题
在实现 Range 请求支持后,我发现当用户请求视频文件时,会出现以下错误:
Error: 416 Requested Range Not Satisfiable
经过一番排查,我发现问题出在 egg-static 对 Range 请求头的处理上。在 egg-static 中,Range 请求头的处理逻辑如下:
if (range && range.type === 'bytes') {
const start = range.start;
const end = range.end;
const fileLength = fs.statSync(file).size;
if (start > fileLength || end > fileLength) {
return ctx.status = 416;
}
ctx.set('Content-Range', `bytes ${start}-${end}/${fileLength}`);
ctx.set('Content-Length', end - start + 1);
return fs.createReadStream(file, { start, end }).pipe(ctx.res);
}
这段代码首先检查 Range 请求头中的 start 和 end 参数是否大于文件长度,如果大于则返回 416 状态码。然后设置 Content-Range 和 Content-Length 头,最后通过 fs.createReadStream 创建一个可读流,并将该流管道到 ctx.res。
解决方案
经过分析,我发现问题的原因是当用户请求的 Range 超出文件长度时,egg-static 并没有正确处理。根据 HTTP 规范,当 Range 请求超出文件长度时,服务器应该返回 206 Partial Content 状态码,而不是 416 Requested Range Not Satisfiable。
为了解决这个问题,我修改了 egg-static 的 Range 请求头处理逻辑,如下:
if (range && range.type === 'bytes') {
const start = range.start;
const end = range.end;
const fileLength = fs.statSync(file).size;
if (start > fileLength || end > fileLength) {
return ctx.status = 206;
ctx.set('Content-Range', `bytes */${fileLength}`);
}
ctx.set('Content-Range', `bytes ${start}-${end}/${fileLength}`);
ctx.set('Content-Length', end - start + 1);
return fs.createReadStream(file, { start, end }).pipe(ctx.res);
}
修改后的代码首先检查 Range 请求头中的 start 和 end 参数是否大于文件长度,如果大于则返回 206 状态码,并设置 Content-Range 头为 bytes */${fileLength}
。然后设置 Content-Length 头,最后通过 fs.createReadStream 创建一个可读流,并将该流管道到 ctx.res。
这样,当我再次请求视频文件时,就不再出现 416 错误了。
总结
通过这次报错的处理,我学到了很多东西。首先,我了解了 egg-static 对 Range 请求头的处理逻辑。其次,我了解了 HTTP 规范中对 Range 请求头的规定。最后,我学会了如何修改 egg-static 的源代码来满足我的需求。
我相信,这次经历对我的成长很有帮助。