Nodejs/express - поток stdout мгновенно клиенту

Я породил следующего ребенка: 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 пингов для завершения, а затем печатает результат в целом. Я хотел бы иметь их один за другим, как это сделать?

(Клиент просто делает вызов .../path и console.logs результат)


РЕДАКТИРОВАТЬ: Хотя я действительно считаю, что для их реализации необходимы веб-узлы, я просто хочу знать, есть ли другие способы. Я видел несколько запутанных ответов SO и записи в блогах (в этот пост, в шаг один OP передает журналы в браузер), которые не помогли, поэтому я решил пойти на щедрость для некоторого внимания.

Ответы

Ответ 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.

Ответ 2

Если вы используете Google Chrome, изменение типа контента на "text/event-stream" делает то, что вы ищете.

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

См. мой пример для полного примера: https://gist.github.com/sfarthin/9139500

Ответ 3

Этого не может быть достигнуто с помощью стандартного цикла HTTP-запроса/ответа. В основном то, что вы пытаетесь сделать, это создать "push" или "realtime" сервер. Это может быть достигнуто только с помощью 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() в этот обработчик событий, вы получите только первый пинг - это ожидаемое поведение, потому что вы заканчиваете поток ответов после первого фрагмента данных из stdout.

Пример кода 2:

spw.stdout.pipe(res);

Здесь stdout выдает пакеты в браузер, но браузер не будет отображать фрагменты данных, пока не будут получены все пакеты. Таким образом, причина, по которой она ждет 10 секунд, а затем выводит всю полноту stdout. Основное преимущество этого метода заключается не в буферизации ответа в памяти перед отправкой, а в том, что ваш вес занимает меньше места.