Sukima
Sukima

Reputation: 10064

How do I close stdin in Node?

I am learning Node.js and thought I had a very simple script but no about of tweaking will ever get my script from hanging forever.

Say I have a dumb server running:

$ nc -l 32001 <<EOF
HTTP/1.1 200 OK
Content-Type: text/plain

Works
EOF

And I run the following script:

var http = require('http');
var options = {
  hostname: 'localhost',
  port: 32001,
  method: 'POST',
  headers: {'Content-Type': 'text/plain'}
};
var req = http.request(options, res => {
  var exitCode = res.statusCode >= 200 && res.statusCode < 300 ? 0 : 1;
  res.pipe(process.stdout).on('end', () => process.exit(exitCode));
});
req.on('error', error => console.error(error));
process.stdin.pipe(req)
  .on('end', () => console.log('this does trigger'));

But when I perform the following:

$ echo foobar | node my-script.js

It just hangs and never reaches the request callback. I would expect the req stream to end and the http.request callback to be called and then that outputs Works and finally exits the process.

I've check that the end event is indeed called from process.stdin.pipe() and I have attempted to manually end the req stream in the end callback. But it just will not end. How do I pipe stdin to an http.request and still have it end the stream?

Upvotes: 8

Views: 9726

Answers (1)

hunteke
hunteke

Reputation: 3716

You are on the right track; you just need to attach your listeners to the proper object events. In the first case (as written, not as executed):

res.pipe(process.stdout).on('end', () => process.exit(exitCode));

What you said: "Pipe the data from response to stdout, and when stdout ends, exit the process."

What you meant: "Pipe the data from response to stdout. When response ends, exit the process." Codified:

res.pipe(process.stdout);
res.on('end', () => process.exit(exitCode));

The point of clarity here is that unless you specifically close process.stdout, it will never close/end until you exit the program. The response however, will end when the HTTP interaction is complete.

It goes similar for the second case:

process.stdin.pipe(req)
  .on('end', () => console.log('this does trigger'));

What you said: "Pipe the data from stdin to request, and when request ends, write a message."

What you meant: "Pipe the data from stdin to request. When stdin ends, write a message." Codified:

process.stdin.pipe(req);
process.stdin.on('end', () => console.log('this does trigger'));

It's slightly more nuanced here because you could listen for either stdin's end event or the request's finish event:

process.stdin.pipe(req).on('finish', () => console.log('Request has finished writing/sending');
process.stdin.on('end', () => console.log('Stdin has no more data'));

For completeness then, your working client back to you, with some mild text modifications for assumed pedagogical clarity:

var http = require('http');
var options = {
  hostname: 'localhost',
  port: 32001,
  method: 'POST',
  headers: {'Content-Type': 'text/plain'}
};
var req = http.request(options, res => {
  var exitCode = res.statusCode >= 200 && res.statusCode < 300 ? 0 : 1;
  res.pipe(process.stdout);
  res.on('end', () => {
    console.log('Response (IncomingMessage) has no more data; exiting with code:', exitCode);
    process.exit(exitCode);
  });
});
req.on('error', error => console.error(error));
process.stdin.on('end', () => console.log('Stdin has no more data.'));
process.stdin.pipe(req).on('finish', () => console.log('Request has finished writing/sending'));

Which outputs at command line:

$ echo Some text from stdin | node test.js; echo $?
Stdin has no more data.
Request has finished writing/sending
Works
Response (IncomingMessage) has no more data; exiting with code: 0
0

And at "the server":

$ nc -l 32001 <<EOF
HTTP/1.1 200 OK
Content-Type: text/plain

Works
EOF
POST / HTTP/1.1
Content-Type: text/plain
Host: localhost:32001
Connection: close
Transfer-Encoding: chunked

15
Some text from stdin

0

Upvotes: 7

Related Questions