【问题标题】:Having trouble streaming response to client using expressjs使用 expressjs 流式传输对客户端的响应时遇到问题
【发布时间】:2014-10-02 02:08:47
【问题描述】:

在使用 Nodejs/Expressjs 时,我很难弄清楚如何将数据流式传输回我的客户端。

我正在从我的数据库中获取大量数据,并且我正在分块进行,我想在获取数据时将其流式传输回客户端,这样我就不必将整个数据集存储在内存中一个 json 对象,然后再将其发送回去。

我希望数据以文件的形式流回,即我希望浏览器询问我的用户如何处理下载的文件。我之前正在创建一个文件系统写入流并将我的数据内容流式传输到文件系统,然后完成后我会将文件发送回客户端。我想消除中间人(在文件系统上创建 tmp 文件),只需将数据流式传输到客户端。

app.get(
    '/api/export',
    function (req, res, next) {
        var notDone = true;
        while (notDone) {
            var partialData = // grab partial data from database (maybe first 1000 records);

            // stream this partial data as a string to res???

            if (checkIfDone) notDone = false;
        }
    }
);

我可以调用 res.write("some string data"),然后在完成后调用 res.end()。但是,我不能 100% 确定这实际上是在我编写时将响应流式传输到客户端。似乎 expressjs 正在存储所有数据,直到我调用 end 然后发送响应。这是真的吗?

使用 expressjs 将字符串数据块流式传输到响应的正确方法是什么?

【问题讨论】:

    标签: node.js express streaming


    【解决方案1】:

    您可以通过设置适当的标头然后写入响应对象来做到这一点。示例:

    res.writeHead(200, {
      'Content-Type': 'text/plain',
      'Content-Disposition': contentDisposition('foo.data')
    });
    var c = 0;
    var interval = setInterval(function() {
      res.write(JSON.stringify({ foo: Math.random() * 100, count: ++c }) + '\n');
      if (c === 10) {
        clearInterval(interval);
        res.end();
      }
    }, 1000);
    
    
    // extracted from Express, used by `res.download()`
    function contentDisposition(filename) {
      var ret = 'attachment';
      if (filename) {
        filename = basename(filename);
        // if filename contains non-ascii characters, add a utf-8 version ala RFC 5987
        ret = /[^\040-\176]/.test(filename)
          ? 'attachment; filename="' + encodeURI(filename) + '"; filename*=UTF-8\'\'' + encodeURI(filename)
          : 'attachment; filename="' + filename + '"';
      }
    
      return ret;
    }
    

    此外,Express/node 不会缓冲写入套接字的数据,除非套接字暂停(显式或隐式由于背压)。处于暂停状态时由节点缓冲的数据可能与已缓冲的其他数据块合并,也可能不合并。您可以检查res.write() 的返回值以确定您是否应该继续写入套接字。如果返回 false,则监听 'drain' 事件,然后再次继续写入。

    【讨论】:

    • 所以 res.write() 将开始发送到客户端,甚至在 res.end() 被调用之前?希望...
    • 是的。除非res.write() 返回false,否则它将立即发送到套接字。
    【解决方案2】:

    response 对象已经是一个可写流。 Express 会自动处理分块数据的发送,因此您无需执行任何额外操作,但:

    response.send(data)

    您可能还想查看内置管道方法http://nodejs.org/api/stream.html#stream_event_pipe。

    【讨论】:

    • response.send(data) 本质上是response.end(data),所以一旦你调用它,你就不能再流式传输任何数据了。
    • 但是我可以做很多次 res.write(data) 对吗?然后 res.end() 完成后。
    • 你可以,但是如果你最终写得太多太快,节点可能最终会将该数据缓冲到内存中,直到套接字准备好发送更多数据。
    • 当然,但关键是如果我写一次(所有数据),res.end(data) 和数据是数百万分贝的数据行,我可能会在我的服务器上耗尽内存。更好的解决方案似乎是从数据库中以块(页面)的形式查询数据,并在将每个小块写入流时写入。
    • RE: mscdex 的评论 - 建议在生产中操作时使用像 nginx 这样的代理。这将为缓冲缓慢的客户端请求和响应提供一个终止层。它允许配置限制,从而丢弃太慢的客户端连接。
    猜你喜欢
    • 2017-07-02
    • 1970-01-01
    • 2020-01-31
    • 1970-01-01
    • 2023-03-02
    • 2018-10-21
    • 2018-07-01
    • 1970-01-01
    • 2015-11-29
    相关资源
    最近更新 更多