为啥 fs.createReadStream ... pipe(res) 锁定读取文件?

Posted

技术标签:

【中文标题】为啥 fs.createReadStream ... pipe(res) 锁定读取文件?【英文标题】:Why is fs.createReadStream ... pipe(res) locking the read file?为什么 fs.createReadStream ... pipe(res) 锁定读取文件? 【发布时间】:2016-12-27 06:08:33 【问题描述】:

我正在使用 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),就会出现不同的快速请求来删除文件,文件保留在文件系统上,直到 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'。然后当用户点击播放视频/音频时,会发出第二次请求,这第二次,'end''close' 事件都会触发,随后删除文件成功。

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

我还发现关闭浏览器也会导致'end''close' 事件触发,并且文件被解锁。我的猜测是我对快递 res 做错了什么,使其无法正常关闭,但我仍然不确定那可能是什么。

【问题讨论】:

【参考方案1】:

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

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

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

正如 OP 中引用的其他答案中所述,chrome 将发送一个带有范围标头的请求,如下所示:

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。

【讨论】:

【参考方案2】:

在选项中设置 autoClose = true。如果 autoClose = false 您必须在“结束”事件中手动关闭它。

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

【讨论】:

我对这个答案持乐观态度,因为我已经阅读了这些文档,但忽略了我在选项中传递了 startend 的事实。但是,即使将 autoClose: true 添加到第二个选项参数后,问题仍然存在。同样值得注意的是,'end' 甚至并不总是触发。

以上是关于为啥 fs.createReadStream ... pipe(res) 锁定读取文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 fs.createReadstream 与 fs.promises 一起使用

Node.js 中的 fs.ReadStream 和 fs.createReadStream 有啥区别吗?

如何防止 fs.createReadStream 打开超过 x 个文件?

fs.createreadstream 中是不是有类似“结束”的“开始”?

node.js 中 fs.createReadStream 与 fs.readFile 的优缺点是啥?

request() 和 fs.createReadStream() 没有返回“正确”的值