How to pipe from standard input to an http request in NodeJS? - node.js

I have an http server listening on port 9090 - piping the request to stdout like so:
let server = http.createServer((req, res) => {req.pipe(process.stdout)})
server.listen(9090)
When I send it something with curl like so:
curl -XGET -T - 'http://localhost:9090' < /tmp/text-input
it works, and I see the output on the server's terminal
but when I try the following in node:
const http = require('http')
const nurl = new URL("http://localhost:9090")
let request = http.request(nurl)
request.on('response', (res) => {
process.stdin.pipe(request)
})
request.end() // If I emit this, nothing happens. If I keep this, I get the below error
and try to run it like so: node request.js < /tmp/text-input, I'm getting the following error:
node:events:368
throw er; // Unhandled 'error' event
^
Error [ERR_STREAM_WRITE_AFTER_END]: write after end
at new NodeError (node:internal/errors:371:5)
at write_ (node:_http_outgoing:748:11)
at ClientRequest.write (node:_http_outgoing:707:15)
at ClientRequest.<anonymous> (/home/tomk/workspace/js-playground/http.js:17:7)
at ClientRequest.emit (node:events:390:28)
at HTTPParser.parserOnIncomingClient (node:_http_client:623:27)
at HTTPParser.parserOnHeadersComplete (node:_http_common:128:17)
at Socket.socketOnData (node:_http_client:487:22)
at Socket.emit (node:events:390:28)
at addChunk (node:internal/streams/readable:324:12)
Emitted 'error' event on ClientRequest instance at:
at emitErrorNt (node:_http_outgoing:726:9)
at processTicksAndRejections (node:internal/process/task_queues:84:21) {
code: 'ERR_STREAM_WRITE_AFTER_END'
}
I want to pipe my stdin to an http server the same way I can with curl -T -. What is wrong with my request code?

Short answer
To send chunked encoding messages in node, use the POST method:
let request = http.request(url, { method: 'POST' })
process.stdin.pipe(request)
Edit: A more straigt forward approach
Or, to send any request method with chunked encoding:
let request = http.request(url)
request.setHeader("transfer-encoding", "chunked")
request.flushHeaders()
process.stdin.pipe(request)
Slightly longer (yet partial) answer
I opened a listening netcat (listen on plain tcp) like so nc -l 9090 to view how the request from curl differs from my code and found a few key differences in the headers.
In curl, the header Transfer-Encoding: chunked appeared, but was missing from the request my code sent out. Also, my code had a header Connection: closed
I logged the request object and found that useChunkedEncodingByDefault is set to false, which was confusing given the quote from the nodejs http docs:
Sending a 'Content-Length' header will disable the default chunked encoding.
Implying that it should be the default.
But then I found this in the source of node
if (method === 'GET' ||
method === 'HEAD' ||
method === 'DELETE' ||
method === 'OPTIONS' ||
method === 'TRACE' ||
method === 'CONNECT') {
this.useChunkedEncodingByDefault = false;
} else {
this.useChunkedEncodingByDefault = true;
}
Edit
To send chunked encoding anyway, I (eventually) found that I need to explicitly add the Transfer-Encoding: Chunked header explicitly:
request.setHeader("transfer-encoding", "chunked")
# and then
request.flushHeaders()
So, in conclusion, node doesn't allow sending send by default GET requests with chunked encoding, but curl does. Odd, and unfortunately not documented (as far as I could find), but the important thing I got it working

Related

https.get not working to access a public API with Nodejs

I'm currently trying to access the JokeAPI using Node's module HTTPS, where below is my code:
const https = require('https');
let url = 'https://v2.jokeapi.dev/joke/Any';
https.get(url, function (res) {
console.log(res);
})
However, somehow I keep getting this error:
node:events:498
throw er; // Unhandled 'error' event
^
Error: self signed certificate in certificate chain
at TLSSocket.onConnectSecure (node:_tls_wrap:1530:34)
at TLSSocket.emit (node:events:520:28)
at TLSSocket._finishInit (node:_tls_wrap:944:8)
at TLSWrap.ssl.onhandshakedone (node:_tls_wrap:725:12)
Emitted 'error' event on ClientRequest instance at:
at TLSSocket.socketErrorListener (node:_http_client:442:9)
at TLSSocket.emit (node:events:520:28)
at emitErrorNT (node:internal/streams/destroy:157:8)
at emitErrorCloseNT (node:internal/streams/destroy:122:3)
at processTicksAndRejections (node:internal/process/task_queues:83:21) {
code: 'SELF_SIGNED_CERT_IN_CHAIN'
Is it because I don't have a server set up? I'm a beginner with JS and Node, so any help would be very appreciated. Thank you!
Edit: I actually added 'process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';' to the top of the code and it worked, but now I'm getting an 'undefined' for a response body. And I'm not sure why that is happening :(
My system does not show the self signed certificate in certificate chain error, but it sounds like you have a work-around for that.
However, now I'm getting an 'undefined' for a response body. I'm not too sure what's causing this.
https.get() just sends the request and reads the headers. It does not, by itself, read the whole response. To do that, you need to listen for the data event on the res object. Installing the listener for the data event will cause the stream to start reading data from the incoming request. This data will not necessarily come all at once. It can come broken into multiple data events so you would really need to accumulate all the data and then in the end event, you know you have all the data. You can see a full example for how to implement that here in the http.get() doc.
Here's a simpler version that will just show you what's in each data event.
const https = require('https');
let url = 'https://v2.jokeapi.dev/joke/Any';
https.get(url, function(res) {
res.on('data', data => {
console.log(data.toString());
});
});
FYI, I no longer use plain https.get() because all the higher level libraries listed here are more convenient to use and all support promises which is generally an easier way to program with asynchronous requests.
My favorite library from that list is got(), but you can choose whichever one you like the API and/or features for. Here's an implementation using got():
const got = require('got');
const url = 'https://v2.jokeapi.dev/joke/Any';
got(url).json().then(result => {
console.log(result);
}).catch(err => {
console.log(err);
});

Log request that caused error in node modules

I run a NodeJS server with two new error types in the logs:
[2021-05-21T09:11:33.891Z] SyntaxError: Unexpected token h in JSON at position 0
at JSON.parse (<anonymous>)
at createStrictSyntaxError (~/server/node_modules/body-parser/lib/types/json.js:158:10)
at parse (~/server/node_modules/body-parser/lib/types/json.js:83:15)
at ~/server/node_modules/body-parser/lib/read.js:121:18
at invokeCallback (~/server/node_modules/raw-body/index.js:224:16)
at done (~/server/node_modules/raw-body/index.js:213:7)
at IncomingMessage.onEnd (~/server/node_modules/raw-body/index.js:273:7)
at IncomingMessage.emit (events.js:323:22)
at IncomingMessage.EventEmitter.emit (domain.js:482:12)
at endReadableNT (_stream_readable.js:1204:12)
at processTicksAndRejections (internal/process/task_queues.js:84:21)
The stacktrace shows only node_modules paths, not where in my code this error may have started. The stdout logs do not show what could have originated this error around that time either.
The server code that handles JSON objects is:
// Use JSON parser (required to parse POST forms)
app.use((req, res, next) => {
bodyParser.json()(req, res, next);
});
app.use(bodyParser.urlencoded({extended: true}));
I added logging inside this function in case I have the same error in the future.
In general, how can I log information about the request that caused an error in the node modules?
update with client-side code
This error originated from a user and I am unable to replicate it. The client-side code sending JSON data is:
// `id` indicates the ID of the video
var body = {
percent: percent,
videoId: id,
eventLabel: eventLabel
}
async function view() {
return await fetch("/viewership", {
method: "POST",
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(body)
});
};
The Network tab of the Chrome debugger shows this request payload:
{percent: 0, videoId: ..., eventLabel: "play"}
Well, here's what we know.
The code is processing an IncomingMessage (and incoming http request)
The error comes from the body-parser module
The error comes from JSON.parse() on what is apparently supposed to be a JSON body
The error appears to come from this particular section of code in the body-parser module.
That code is this:
if (strict) {
var first = firstchar(body)
if (first !== '{' && first !== '[') {
debug('strict violation')
throw createStrictSyntaxError(body, first)
}
}
So, it is apparently failing to find a leading { or [ on the JSON and is instead finding an h.
We can deduce from that information that an incoming http request (probably a POST) is supposed to have a JSON body, but the data is not legal JSON.
Your first point of debugging is to see exactly the JSON body data is in the request. If this request is coming from a browser, you can look in the Chrome network tab of the debugger and see exactly what the browser is sending your server.
So, this is most likely a client-caused error. Either the content-type is set wrongly to JSON when the data is not JSON or the client is supposed to be sending JSON, but is not sending proper JSON.
If you can show us the client-side code for this, we may be able to spot the error in that code.
Do you know a way to log any request that throws an error, e.g. for other bad requests in the future unrelated to JSON?
When the body-parser gets bad JSON, it calls the Express error handler with the exception. If you go the the "Writing Error Handlers" on this Express doc page, it will show you how to catch these errors and handle them with some error page back to the client and as much logging as you want.

Workaround from client side for invalid HTTP response headers that contain both Transfer-Encoding and Content-Length?Can see response using Postman

I'm calling an API that returns 5 keys in the header including Transfer-encoding:chunked and Content-Length values, in the header.
According to stackoverflow question, and this below, this is illegal:
"...Node.js server returns a parse error with code HPE_UNEXPECTED_CONTENT_LENGTH when consuming the endpoint, because the response headers contains both Transfer-encoding:chunked and Content-Length values.
This is considered has an error as specified in RFC 7230 section 3.3.3.3 :
If a message is received with both a Transfer-Encoding and a Content-Length header field, the Transfer-Encoding overrides the Content-Length. Such a message might indicate an attempt to perform request smuggling (Section 9.5) or response splitting (Section 9.4) and ought to be handled as an error. A sender MUST remove the received Content-Length field prior to forwarding such a message downstream." (1)
I'm using node.js and npm-request module to send the request and try and parse the response. The function being called is
function createPostAPI(req, res, userid, detailText, title, location, custImpact){
var myJSONObject = {
"userId" : userid,
"apiKey" : "{API-Key}",
"detail" : {
"detailText" : detailText
},
"info" : {
"title":title
"location" : location,
"priority" : 0,
"customerImpact" : custImpact
}
}
request1({
url: "http://theAPIurl",
method: "POST",
body:JSON.stringify(myJSONObject)
json: true, // <--Very important!!!
body: myJSONObject
}, function (error, response, body){
console.log(error);
console.log(response);
console.log(body);
});
}
This is the error i get as expected :
{ Error: Parse Error
at Error (native)
at Socket.socketOnData (_http_client.js:362:20)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
at readableAddChunk (_stream_readable.js:176:18)
at Socket.Readable.push (_stream_readable.js:134:10)
at TCP.onread (net.js:547:20) bytesParsed: 194, code: 'HPE_UNEXPECTED_CONTENT_LENGTH' }
However, I can see that the API call is working, since I can visually verify that it's working! Also using Postman I can see the response that I need!
Postman Response:
The header with the 2 headers (content-length and transfer-encoding):
The response body that I need that i can see being returned:
So finally my question is, is there anything i can do from my side (client) to avoid the error and read that http response from the server (even though the format is wrong)? How come I can see the body in Postman? Any ideas would be truly appreciated!
Using node v6.10.3
Reference:
(1) https://jira.spring.io/browse/SPR-15212

Reject invalid/unexpected request on sockets

How do I make sure that any invalid or unwanted request that isn't following sockets protocol gets rejected and my socket aren't closed.
Consider a simple example :
var net = require('net');
net.createServer(function(socket) {
socket.on('data', function(data) {
var parsedData = JSON.parse(data.toString());
});
}).listen(5555);
server.listen(1337, '127.0.0.1');
and try visiting that port from browser , and again look at terminal. It produces following error :
undefined:1
GET / HTTP/1.1
^
SyntaxError: Unexpected token G
at Object.parse (native)
at Socket.<anonymous> (/home/agauniyal/projects/watch/watch.js:115:35)
at Socket.emit (events.js:107:17)
at readableAddChunk (_stream_readable.js:163:16)
at Socket.Readable.push (_stream_readable.js:126:10)
at TCP.onread (net.js:529:20)
Now I fully agree that visiting that port is a mistake of mine , but closing a socket on different format rather than rejecting that request isn't the ideal way to do it. And what if someone tries to access a webserver at that port only realizing later that there wasn't any at 8080.
So how should I make sure socket isn't closed and rather rejects that request?
EDIT : This is possibily happening because I'm calling JSON.Parse(data.toString()) method on data being received from socket and http headers aren't being parsed by that method.
Entire problem rose because I was getting socket response , converting it to string and then parsing it as JSON. Now in my case , I've either to gurantee that incoming data is valid JSON string or find that a browser is sending request to that socket.
For a real-quick-fix , I am extracting first word from incoming data and checking it to be 'GET' ( which a browser sends ) , and if it evaluates to true , just doing nothing else proceeding as I was earlier. Catching JSON.parse error is better solution too.

node.js distinguishing errors when making http request

My node.js application is using http.request to the REST API http://army.gov/launch-nukes and I need to distinguish between three possible cases:
Success -- The server replies in the affirmative. I know my enemies are destroyed.
Failure -- Either I have received error from the server, or was unable to connect to server. I still have enemies.
Unknown -- After establishing a connection to the server, I have sent the request -- but not sure what happened. This could mean the request never made it to the server, or the server response to me never made it. I may or may not have just started a world war.
As you can see, it's very important for me to distinguish the Failure and Unknown case, as they have very different consequences and different actions I need to take.
I would also very much like to use http Keep-Alive -- as what can I say, I'm a bit of a war-monger and plan on making lots of requests in bursts (and then nothing for long periods of time)
--
The core of the question is how to separate a connection-error/time-out (which is a Failure) from an error/timeout that occurs after the request is put on the wire (which is an Unknown).
In psuedo-code logic I want this:
var tcp = openConnectionTo('army.gov') // start a new connection, or get an kept-alive one
tcp.on('error', FAILURE_CASE);
tcp.on('connectionEstablished', function (connection) {
var req = connection.httpGetRequest('launch-nukes');
req.on('timeout', UNKNOWN_CASE);
req.on('response', /* read server response and decide FAILURE OR SUCCESS */);
}
)
Here is an example:
var http = require('http');
var options = {
hostname: 'localhost',
port: 7777,
path: '/',
method: 'GET'
};
var req = http.request(options, function (res) {
// check the returned response code
if (('' + res.statusCode).match(/^2\d\d$/)) {
// Request handled, happy
} else if (('' + res.statusCode).match(/^5\d\d$/))
// Server error, I have no idea what happend in the backend
// but server at least returned correctly (in a HTTP protocol
// sense) formatted response
}
});
req.on('error', function (e) {
// General error, i.e.
// - ECONNRESET - server closed the socket unexpectedly
// - ECONNREFUSED - server did not listen
// - HPE_INVALID_VERSION
// - HPE_INVALID_STATUS
// - ... (other HPE_* codes) - server returned garbage
console.log(e);
});
req.on('timeout', function () {
// Timeout happend. Server received request, but not handled it
// (i.e. doesn't send any response or it took to long).
// You don't know what happend.
// It will emit 'error' message as well (with ECONNRESET code).
console.log('timeout');
req.abort();
});
req.setTimeout(5000);
req.end();
I recommend you play with it using netcat, ie.:
$ nc -l 7777
// Just listens and does not send any response (i.e. timeout)
$ echo -e "HTTP/1.1 200 OK\n\n" | nc -l 7777
// HTTP 200 OK
$ echo -e "HTTP/1.1 500 Internal\n\n" | nc -l 7777
// HTTP 500
(and so on...)

Resources