web-dev-qa-db-ja.com

nodejs / express-stdoutをクライアントに即座にストリーミングします

次の子を生成しました:var spw = spawn('ping', ['-n','10', '127.0.0.1'])そしてクライアント側でpingの結果を受け取りたい(browser全体ではなく、1つずつ

これまで私はこれを試しました:

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の結果が記録されます)


EDIT:これを実装するにはWebSocketが必要だと思いますが、他に方法があるかどうかを知りたいだけです。いくつかの紛らわしい SO回答 とブログ投稿( this 投稿で、ステップ1のOPがログをブラウザにストリーミングする)を見たので、役に立たなかったので、いくつかの注意のために賞金を取りに行きます。

19
anvarik

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を指定します。 OS Xを実行しているので、「-n」ではなく「-c」オプションを使用する必要があることに注意してください。

27
nimrodm

Google Chromeを使用している場合は、コンテンツタイプを「text/event-stream」に変更すると、探しているものが実行されます。

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

完全な例については、私の要点を参照してください: https://Gist.github.com/sfarthin/91395

2
Steve Farthing

これは、標準のHTTP要求/応答サイクルでは実現できません。基本的にあなたがやろうとしていることは、「プッシュ」または「リアルタイム」サーバーを作ることです。これは、xhr-pollingまたはwebsocketでのみ実現できます。

コード例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);
_

ここで、stdoutはパケットをブラウザにフラッシュしていますが、ブラウザはすべてのパケットが受信されるまでデータチャンクをレンダリングしません。したがって、10秒間待機してから、stdout全体をレンダリングする理由。この方法の主な利点は、送信前に応答をメモリにバッファリングしないことです。つまり、メモリフットプリントを軽量に保つことができます。

1
srquinn