How to guarantee that exactly one callback is fired in a simple Node.js HTTP client after ECONNRESET, not twice?

117 views Asked by At

Consider the following HTTP client, following official example of the http module`:

var http = require('http');
http.get('http://127.0.0.1:12345/', function(response) {
    console.log('got response ', response.statusCode);
    var body = [];
    response.on('data', function(data) {
        console.log('got data: ', data);
    });
    response.on('end', function() {
        body = Buffer.concat(body).toString();
        console.log('end body = ', body, ' complete = ', response.complete);
        console.log('success!');
    });
}).on('error', function(err) {
    console.log('ClientRequest error: ' + err);
    console.log('error!');
});

Here I make an HTTP request, and report on three occasions: whenever I start receiving an HTTP response, whenever I get some HTTP body data, whenever the response is complete, and on all errors.

I expect this code to always complete (unless the connection hangs) with exactly one of success! or error! in logs. However, if the connect resets after the server has already sent some headers, I get both. Here is an example output:

got response  200
got data:  <Buffer 48 65 6c 6c 6f 0a>
ClientRequest error: Error: read ECONNRESET
error!
end body =  Hello
  complete =  true
success!

How to reproduce:

  1. nc -l -p 12345 to start a debug server.
  2. Run the client with node a.js. I use node v18.14.2 on Windows, but I saw the same behavior on Ubuntu 22.04 with node v12.22.9 as well.
  3. Type the following response, but do not abort nc just yet:
    HTTP/1.1 200 OK
    
    Hello
    
  4. Terminate the nc process with task manager (Windows) or kill the TCP connection with sudo tcpkill -i lo port 12345 and writing something else to nc (Linux).

It seems like Node both detects the error (hence the error callback is fired) and considers the HTTP request a success (hence to end callback is fired). In my original code that resulted in two instances of my application continuing instead of one which was very confusing.

How do I make sure exactly one of those is fired, and only on successful HTTP responses?

1

There are 1 answers

4
Amrita Bithi On

I suggest taking a look at the once() function which removes itself after being called, here is a link to it's reference - https://nodejs.org/api/events.html#emitteronceeventname-listener

Also, your code seems to be a get-event handler on a server but is behaving as though it is reading data from a server, and so I am not sure if you have confused the two here or if I am mistaken, however I would also recommend taking a look at using something like 'Node Fetch' that simplifies your program execution flow - https://www.npmjs.com/package/node-fetch

And looks something like this instead of the traditional event handlers:

import fetch from 'node-fetch';

const response = await fetch('https://github.com/');
const body = await response.text();

console.log(body);