26

Here is my snippet I tested it in Chrome 11, and Firefox 4:

var http = require('http');

http.createServer(function(request, response){
   // Write Headers
   response.writeHead(200);

   // Write Hello World!
   response.write("Hello World!");

   // End Response after 5 seconds
   setTimeout(function(){ 
        response.end(); 
   }, 5000);

}).listen(8000);

As you can see I timed out the response.end() so I can test if response.write is outputted before the response.end. In my experience though it is not.

Is there a way to output the data before ending the response, something like sending the data in packets?

Adam Halasz
  • 57,421
  • 66
  • 149
  • 213

4 Answers4

19

There actually is a way that you can do this without setting Content-Type: text/plain and still use text/html as the Content-Type, but you need to tell the browser to expect chunks of data.

This can be done easily like this:

var http = require('http');

http.createServer(function(request, response) {

    response.setHeader('Connection', 'Transfer-Encoding');
    response.setHeader('Content-Type', 'text/html; charset=utf-8');
    response.setHeader('Transfer-Encoding', 'chunked');

    response.write('hello');

    setTimeout(function() {
        response.write(' world!');
        response.end();
    }, 10000);

}).listen(8888);


You should be aware though, that until response.end() is called the request is still taking place and blocking other requests to your nodejs server.
You can easily test this by opening calling this page (localhost:8888) on two different tabs. One of them will wait 10 seconds, and the other will only get the beginning of the response once the first response ends (meaning you'll wait 10 seconds for the beginning of the response and another 10 seconds till the end of the response, using this code).

You can probably pass this obstacle too by running a couple of nodejs processes and load balancing between them, but then this begins to get much more complicated, and is a thread that should be taken else where... :)

gillyb
  • 8,760
  • 8
  • 53
  • 80
  • Thanks, this works for me! So the trick is not in the content-type header but in the 'Transfer-Encoding' attribute in the header. – Enrico Giurin Jun 04 '15 at 09:34
  • 1
    It doesn't block until response.end! I thought the same thing! But make an endpoint that returns today's date, like this `if (request.url === '/today') { response.end('Today: ' + new Date()); return; }`. Then, hit `http:localhost:8888` and then hit `http:localhost:8888/today` and you'll see you can get the latest date right away while the other request is running for 10 seconds. – JohnnyFun Jul 24 '15 at 05:07
  • I think the issue with your test is that chrome still refuses to render the damned content, despite your headers. I'm still working on figuring that part out completely though... – JohnnyFun Jul 24 '15 at 05:09
16

If you change the content type to text/plain -- e.g:

// Write Headers
response.writeHead(200, {'Content-Type': 'text/plain'});

then firefox will show the content immediately. Chrome still seems to buffer (if you write a bunch more content, chrome will show it immediately).

Geoff Chappell
  • 2,432
  • 1
  • 16
  • 12
  • It doesn't work for me with Firefox, I got the response with the two lines in a single shot at the end of timeout. Same with IE. – Enrico Giurin Jun 04 '15 at 09:30
  • 1
    Output buffering can also happen if you have compression enabled for your output. It'll buffer everything so the entire payload can be compressed together. If you're using the `compression` Node module, you can simply add the `Cache-Control: no-transform` header. This will keep `compression` from buffering your output. – Jonah Nov 14 '16 at 23:10
  • 1
    @EnricoGiurin Didn't work for me as well. I just had to specify content-type as "text/html; charset=utf-8". Now it works in Firefox and Chrome as well. You don't even need to set transfer-encoding to chunked to make it work on both browsers. – FeignMan Aug 31 '17 at 14:03
15

If you want to output chunked plain text in Chrome - just like what Firefox is doing by default - you need to use the 'X-Content-Type-Options': 'nosniff' header. See What is "X-Content-Type-Options=nosniff"?

var http = require('http');

http.createServer(function (req, res) {
    res.writeHead(200, {
        'Content-Type': 'text/plain; charset=utf-8',
        'Transfer-Encoding': 'chunked',
        'X-Content-Type-Options': 'nosniff'});
    res.write('Beginning\n');
    var count = 10;
    var io = setInterval(function() {
        res.write('Doing ' + count.toString() + '\n');
        count--;
        if (count === 0) {
            res.end('Finished\n');
            clearInterval(io);
        }
    }, 1000);
}).listen(8888);

You don't need this option if your output is text/html.

Solution found from this Chrome defect: Transfer-Encoding chunked not support on text/plain

Community
  • 1
  • 1
Vlad
  • 411
  • 4
  • 7
3

Here are the primary points you need to take note of:

  • specify a charset
  • each "chunk" will be outputted by the browser (atleast, what I noticed in Chrome) through a new line (<br> if the charset is text/html)

Like so:

res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.write('a<br>');
setTimeout(function() {
  res.write('b<br>');
  setTimeout(function() {
    res.write('c');
    res.end();
  }, 2000);
}, 2000);
Jürgen Paul
  • 14,299
  • 26
  • 93
  • 133