为什么 fs.createReadStream ... pipe(res) 锁定读取的文件?

Why is fs.createReadStream ... pipe(res) locking the read file?

我正在使用 express 传输音频和视频文件 according to this answer。相关代码如下所示:

function streamMedia(filePath, req, res) {
  // code here to determine which bytes to send, compute response headers, etc.

  res.writeHead(status, headers);
  var stream = fs.createReadStream(filePath, { start, end })
    .on('open', function() {
      stream.pipe(res);
    })
    .on('error', function(err) {
      res.end(err);
    })
  ;
}

这可以很好地将字节流式传输到客户端上的 <audio><video> 元素。然而,在处理完这些请求后,另一个明确的请求可以从文件系统中删除正在流式传输的文件。第二个请求有点失败了。

发生的情况是,只要文件至少被流式传输一次(意味着 createReadStream 被调用用于文件路径,而 运行 上面的代码),就会出现不同的快速请求in 删除文件,文件保留在文件系统中,直到 express 停止。一旦 express 停止,文件就会从文件系统中删除。

这里到底发生了什么?锁定文件的是 fs 还是 express,为什么,我怎样才能让进程释放文件以便删除文件(在读取其内容并通过管道传输到响应之后, 如果有待处理)?

更新 1:

我修改了上面的代码,为第二个函数 arg 设置 autoClose: true,并添加了 'end''close' 事件处理程序,如下所示:

res.writeHead(status, headers);
var streamReadOpts = { start: start, end: end, autoClose: true };
var stream = fs.createReadStream(filePath, streamReadOpts)
    // previous 'open' & 'error' event handlers are still here
    .on('end', function () {
      console.log('stream end');
    })
    .on('close', function () {
      console.log('stream close');
    })

我发现当页面最初加载 <video><audio> 元素时,甚至只有 'open' 被触发。然后当用户点击播放 video/audio 时,发出第二次请求,第二次, 'end''close' 事件都会触发,随后删除文件成功。

因此,当用户加载具有 <video><audio> 元素的页面时,文件似乎被锁定,该元素从调用此函数的请求中获取其 source .直到播放该媒体文件才发出第二个请求,文件才被解锁。

我还发现关闭浏览器也会触发 'end''close' 事件,并解锁文件。我的猜测是我在 express res 上做错了什么导致它不能正确关闭,但我仍然不确定那是什么。

在选项中设置 autoClose = true。如果 autoClose = false 你必须在 'end' 事件中手动关闭它。

参考节点文档:- https://nodejs.org/api/fs.html#fs_fs_createreadstream_path_options

事实证明,这个问题的解决方案是在每次请求期间从文件中读取和传输更小的数据块。在我的测试用例中,我流式传输了一个 6MB 的 MP4 视频文件。尽管我能够使用 firefox 或 chrome 重现该问题,但我使用后者进行了调试,发现 客户端阻塞了流 .

最初加载页面时,有一个元素看起来像这样:

<video> <!-- or <audio> -->
    <source src="/path/to/express/request" type="video/mpeg" /> <!-- or audio/mpeg -->
</video> <!-- or </audio> -->

如 OP 中引用的其他答案中所述,chrome 将发送范围为 header 的请求,如下所示:

Range:bytes=0-

对于这个请求,我的函数发送了整个文件,我的响应如下所示:

Accept-Ranges:bytes
Connection:keep-alive
Content-Length:6070289
Content-Range:bytes 0-6070288/6070289
Content-Type:video/mp4

但是,chrome 没有读取整个流。它只读取前 3-4MB,然后阻塞连接,直到用户操作导致它 需要 文件的其余部分。这解释了为什么关闭浏览器或停止 express 导致文件被解锁,因为它关闭了来自浏览器或服务器端的连接。

我目前的解决方案是一次最多发送 1MB(旧学校 1MB,1024 * 1024)块。相关代码见an additional answer to the question referenced in the OP.