【问题标题】:nodejs/express - stream stdout instantly to the clientnodejs/express - 立即将标准输出流式传输到客户端
【发布时间】:2014-03-22 23:47:26
【问题描述】:

我生成了以下孩子:var spw = spawn('ping', ['-n','10', '127.0.0.1']),我希望在客户端(浏览器一个接一个接收 ping 结果,而不是作为一个整体.

到目前为止,我尝试过这个:

app.get('/path', function(req, res) {
   ...
   spw.stdout.on('data', function (data) {
      var str = data.toString();
      res.write(str + "\n");
   });
   ...
}

还有:

...
spw.stdout.pipe(res);
...

在这两种情况下,浏览器都会等待 10 个 ping 完成,然后将结果作为一个整体打印出来。我想一一拥有,如何实现?

(客户端只是调用.../path 并且console.logs 结果)


编辑:虽然我确实相信 websockets 是实现这一点所必需的,但我只想知道是否还有其他方法。我看到几个令人困惑的SO answers 和博客帖子(在this 帖子中,在第一步中,OP 将日志流式传输到浏览器)并没有帮助,因此我决定寻求赏金以引起注意。

【问题讨论】:

    标签: node.js express spawn child-process


    【解决方案1】:

    这是一个使用 SSE(服务器发送事件)的完整示例。这适用于 Firefox,也可能适用于 Chrome:

    var cp = require("child_process"),
             express = require("express"),
             app = express();
    
    app.configure(function(){
        app.use(express.static(__dirname));
    });
    
    
    app.get('/msg', function(req, res){
        res.writeHead(200, { "Content-Type": "text/event-stream",
                             "Cache-control": "no-cache" });
    
        var spw = cp.spawn('ping', ['-c', '100', '127.0.0.1']),
        str = "";
    
        spw.stdout.on('data', function (data) {
            str += data.toString();
    
            // just so we can see the server is doing something
            console.log("data");
    
            // Flush out line by line.
            var lines = str.split("\n");
            for(var i in lines) {
                if(i == lines.length - 1) {
                    str = lines[i];
                } else{
                    // Note: The double-newline is *required*
                    res.write('data: ' + lines[i] + "\n\n");
                }
            }
        });
    
        spw.on('close', function (code) {
            res.end(str);
        });
    
        spw.stderr.on('data', function (data) {
            res.end('stderr: ' + data);
        });
    });
    
    app.listen(4000);
    

    和客户端 HTML:

    <!DOCTYPE Html>
    <html> 
    <body>
       <ul id="eventlist"> </ul>
    
       <script>              
        var eventList = document.getElementById("eventlist");
        var evtSource = new EventSource("http://localhost:4000/msg");
    
        var newElement = document.createElement("li");
        newElement.innerHTML = "Messages:";
        eventList.appendChild(newElement);
    
    
        evtSource.onmessage = function(e) {
            console.log("received event");
            console.log(e);
            var newElement = document.createElement("li");
    
            newElement.innerHTML = "message: " + e.data;
            eventList.appendChild(newElement);
        };      
    
        evtSource.onerror = function(e) {
            console.log("EventSource failed.");
        };
    
        console.log(evtSource);
    
        </script>
    
    </body>
    </html>
    

    运行node index.js 并将浏览器指向http://localhost:4000/client.html。 请注意,我必须使用“-c”选项而不是“-n”,因为我运行的是 OS X。

    【讨论】:

    • 正是我要找的东西,一旦允许我就会给你赏金:) imgur.com/oLLbGXU
    • 当用户从页面导航时取消事件流有什么好的解决方案?
    • @AhmedAhmed 我假设如果用户关闭了网页,则 ​​res.write() 会抛出异常或触发“关闭”事件。您为什么不自己尝试并在此处报告您的结果?
    • 我确实尝试过 :) 从客户端你需要调用 evtSource.close();并且您需要从服务器监听关闭事件 req.connection.addListener("close", function () { console.log('client closed'); spw.stdin.pause(); spw.kill(); });
    • 7 年后它仍然有效 ?,但应该删除 app.configured(只需 app.use(express.static(__dirname)); 就足够了)
    【解决方案2】:

    如果您使用的是 Google Chrome,将内容类型更改为“文本/事件流”即可满足您的要求。

    res.writeHead(200, { "Content-Type": "text/event-stream" });
    

    完整示例请参阅我的要点:https://gist.github.com/sfarthin/9139500

    【讨论】:

    • 你能用 var spw = cp.spawn('ping', ['127.0.0.1', '-n', '10']) 试试你的要点,并确认你可以看到真实的浏览器上的时间控制台也是如此?在@jibsales 回答后,我开始认为实时浏览器只能使用套接字,但您的回答让我感到困惑......
    • 刚试过,不行。如果您发送 10 次 ping,您的解决方案会等到全部完成,然后 chrome 会显示所有...
    • @anvarik:请参阅我的答案中的代码。它基于史蒂夫的建议并且确实有效。
    【解决方案3】:

    这是标准 HTTP 请求/响应周期无法实现的。基本上,您要做的是制作“推送”或“实时”服务器。这只能通过 xhr-polling 或 websockets 来实现。

    代码示例1:

    app.get('/path', function(req, res) {
       ...
       spw.stdout.on('data', function (data) {
          var str = data.toString();
          res.write(str + "\n");
       });
       ...
    }
    

    此代码从不发送结束信号,因此永远不会响应。如果您要在该事件处理程序中添加对 res.end() 的调用,您只会得到第一个 ping - 这是预期的行为,因为您将在 stdout 的第一块数据之后结束响应流。

    代码示例 2:

    spw.stdout.pipe(res);
    

    这里标准输出将数据包刷新到浏览器,但浏览器在收到所有数据包之前不会渲染数据块。因此它等待 10 秒然后渲染整个标准输出的原因。这种方法的主要好处是在发送之前不会在内存中缓冲响应——保持你的内存占用很轻。

    【讨论】:

    • 感谢您的回答。实际上我在spw.on('close'... 中有一个res.end,但只是没有写下来。我和你的想法一样,但这个答案让我很困惑:stackoverflow.com/questions/20357216/…
    • 这个答案是关于将数据分块流式传输到服务器,以避免首先在服务器中缓冲所有数据。是否以块的形式渲染完全取决于浏览器逻辑。
    • 正确——服务器在管道上将其全部发送出去,但浏览器在收到所有数据包之前不会关闭连接。我改变了我的解释来反映这一点——不知道我以前在想什么!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-29
    • 1970-01-01
    • 1970-01-01
    • 2018-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多