57

I'm making a HTTP request and listen for "data":

response.on("data", function (data) { ... })

The problem is that the response is chunked so the "data" is just a piece of the body sent back.

How do I get the whole body sent back?

Pero P.
  • 25,813
  • 9
  • 61
  • 85
ajsie
  • 77,632
  • 106
  • 276
  • 381

8 Answers8

77
request.on('response', function (response) {
  var body = '';
  response.on('data', function (chunk) {
    body += chunk;
  });
  response.on('end', function () {
    console.log('BODY: ' + body);
  });
});
request.end();
Pero P.
  • 25,813
  • 9
  • 61
  • 85
  • 1
    Yeah I tried that too, the problem is that the body will be a json invalid format .. the ending is kinda messed up, I think it's a white space or a new line that makes JSON.parse not able to parse it. – ajsie Feb 22 '11 at 21:17
  • If your body is a json message, you need to parse the string to JSON (e.g. `JSON.parse(body);`) – schaermu Feb 22 '11 at 21:19
  • 3
    Yeah that's the problem. The body generated by concatenating all data is not in valid json format so the JSON.parse() throws an error :( – ajsie Feb 22 '11 at 21:37
  • No its invalid ... the error is just pointing out the invalid format: SyntaxError: Unexpected token ILLEGAL – ajsie Feb 22 '11 at 21:46
  • I solved this by using YUI on Node.js instead. Now I can have the same code on both ends :) – ajsie Feb 22 '11 at 21:47
  • Using `console.log` at this point in the code works. But what if I want to return the response body instead? I don’t know how to do this in Node.js yet… – Zearin Sep 04 '12 at 22:24
  • Return the body where @Zearin? I guess the question is if the receipt of the response body is a trigger to initiate some form of processing or logic? If this is the case you'd just call into a function that does that, passing in the body. – Pero P. Sep 04 '12 at 22:57
  • any one know something like this in android. I tried to find out but haven't found anything. Even posted question http://stackoverflow.com/questions/23149187/handle-response-properly-when-used-httpclient . – keen Apr 18 '14 at 07:24
  • It is crucial that you call response.setEncoding('utf8') before you bind your response.on callbacks. Otherwise you'll end up with weirdly broken utf8, sometimes. –  May 08 '14 at 23:45
  • @PeroP. how does this solution guarantee that the chunks arrive in order? – AlwaysNull Jun 14 '20 at 18:50
  • Notice that the [official Node.js guide](https://nodejs.org/es/docs/guides/anatomy-of-an-http-transaction/#request-body) recommends using `Buffer.concat`...rather than `O(n^2)` string concatenation – ATOMP Aug 10 '20 at 21:16
28

Over at https://groups.google.com/forum/?fromgroups=#!topic/nodejs/75gfvfg6xuc, Tane Piper provides a good solution very similar to scriptfromscratch's, but for the case of a JSON response:

  request.on('response',function(response){
     var data = [];
     response.on('data', function(chunk) {
       data.push(chunk);
     });
     response.on('end', function() {
       var result = JSON.parse(data.join(''))
       return result
     });
   });`

This addresses the issue that OP brought up in the comments section of scriptfromscratch's answer.

Jim Danz
  • 319
  • 3
  • 4
  • 1
    Thanks for this solution. I had the same problem as mentioned in the question, and JSON.parse returned invalid format as the endings were broken. adding data.join('') solved the issue. – varad_s Jun 14 '20 at 19:45
6

I never worked with the HTTP-Client library, but since it works just like the server API, try something like this:

var data = '';
response.on('data', function(chunk) {
  // append chunk to your data
  data += chunk;
});

response.on('end', function() {
  // work with your data var
});

See node.js docs for reference.

schaermu
  • 13,478
  • 2
  • 39
  • 32
5

In order to support the full spectrum of possible HTTP applications, Node.js's HTTP API is very low-level. So data is received chunk by chunk not as whole.
There are two approaches you can take to this problem:

1) Collect data across multiple "data" events and append the results
together prior to printing the output. Use the "end" event to determine
when the stream is finished and you can write the output.

var http = require('http') ;
http.get('some/url' , function (resp) {
    var respContent = '' ;
    resp.on('data' , function (data) {
        respContent += data.toString() ;//data is a buffer instance
    }) ;
    resp.on('end' ,  function() {
        console.log(respContent) ;
    }) ;
}).on('error' , console.error) ;

2) Use a third-party package to abstract the difficulties involved in
collecting an entire stream of data. Two different packages provide a
useful API for solving this problem (there are likely more!): bl (Buffer
List) and concat-stream; take your pick!

var http = require('http') ;
var bl = require('bl') ;

http.get('some/url', function (response) {
    response.pipe(bl(function (err, data) {
        if (err) {
            return console.error(err)
        }
        data = data.toString() ;
        console.log(data) ;
    })) 
}).on('error' , console.error) ;
Salar
  • 5,305
  • 7
  • 50
  • 78
3

The reason it's messed up is because you need to call JSON.parse(data.toString()). Data is a buffer so you can't just parse it directly.

Martin
  • 3,509
  • 3
  • 26
  • 31
3

If you don't mind using the request library

var request = require('request');
request('http://www.google.com', function (error, response, body) {
  if (!error && response.statusCode == 200) {
    console.log(body) // Print the google web page.
  }
})
Dave Zych
  • 21,581
  • 7
  • 51
  • 66
tim
  • 3,191
  • 2
  • 15
  • 17
3

If you are dealing with non-ASCII contents(Especially for Chinese/Japanese/Korean characters, no matter what encoding they are), you'd better not treat chunk data passed over response.on('data') event as string directly.

Concatenate them as byte buffers and decode them in response.on('end') only to get the correct result.

// Snippet in TypeScript syntax:
//
// Assuming that the server-side will accept the "test_string" you post, and 
// respond a string that concatenates the content of "test_string" for many 
// times so that it will triggers multiple times of the on("data") events.
//

const data2Post = '{"test_string": "swamps/沼泽/沼澤/沼地/늪"}';
const postOptions = {
    hostname: "localhost",
    port: 5000,
    path: "/testService",
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(data2Post)  // Do not use data2Post.length on CJK string, it will return improper value for 'Content-Length'
    },
    timeout: 5000
};

let body: string = '';
let body_chunks: Array<Buffer> = [];
let body_chunks_bytelength: number = 0; // Used to terminate connection of too large POST response if you need.

let postReq = http.request(postOptions, (res) => {
    console.log(`statusCode: ${res.statusCode}`);

    res.on('data', (chunk: Buffer) => {
        body_chunks.push(chunk);
        body_chunks_bytelength += chunk.byteLength;

        // Debug print. Please note that as the chunk may contain incomplete characters, the decoding may not be correct here. Only used to demonstrating the difference compare to the final result in the res.on("end") event.
        console.log("Partial body: " + chunk.toString("utf8"));

        // Terminate the connection in case the POST response is too large. (10*1024*1024 = 10MB)
        if (body_chunks_bytelength > 10*1024*1024) {
            postReq.connection.destroy();
            console.error("Too large POST response. Connection terminated.");
        }
    });

    res.on('end', () => {
        // Decoding the correctly concatenated response data
        let mergedBodyChunkBuffer:Buffer = Buffer.concat(body_chunks);
        body = mergedBodyChunkBuffer.toString("utf8");

        console.log("Body using chunk: " + body);
        console.log(`body_chunks_bytelength=${body_chunks_bytelength}`);
    });
});
SONIC3D
  • 489
  • 4
  • 6
0

How about HTTPS chunked response? I've been trying to read a response from an API that response over HTTPS with a header Transfer-Encoding: chunked. Each chunk is a Buffer but when I concat them all together and try converting to string with UTF-8 I get weird characters.

Kamil Oczkowski
  • 125
  • 2
  • 9