Why is res.send called twice?

I am working on creating middleware for an express router that will execute some code for each request and response. Request interception is very simple, and there are many examples, but I did not find anything for an elegant approach to intercepting an answer. After some research, the best I've come up with is to replace the send function of the response object shown in the following snippet:

const express = require('express'); const app = express(); var router = express.Router(); var port = process.env.PORT || 3000; router.get('/test', function(req, res) { res.send({ message: "testing" }); }); app.use(function(req, res, next){ console.log("INTERCEPT-REQUEST"); const orig_send = res.send; res.send = function(arg) { console.log("INTERCEPT-RESPONSE"); orig_send.call(res, arg); }; next(); }); app.use("/api", router); app.listen(process.env.PORT || 3000) && console.log("Running"); 

The problem with this approach: for some reason, "INTERCEPT-RESPONSE" is printed on the console twice, that is, res.send is called twice ...

I can set the flag on res.locals on the first call to avoid processing the response twice, but I wonder why res.send is called twice?

+5
source share
1 answer

Best example

Try this code to find out which arguments are passed to res.send :

 const express = require('express'); const app = express(); var router = express.Router(); var port = process.env.PORT || 3000; router.get('/test', function(req, res) { console.log('ACTUAL RESPONSE'); res.send({ message: "testing" }); }); app.use(function(req, res, next){ console.log("INTERCEPT-REQUEST"); const orig_send = res.send; res.send = function(arg) { console.log("INTERCEPT-RESPONSE", JSON.stringify(arguments)); orig_send.call(res, arg); }; next(); }); app.use("/api", router); app.listen(process.env.PORT || 3000, function () { console.log("Running"); }); 

(I also changed the Startup print to print it when the server is actually listening - your && code was printed before the server was listening - but that is not so important here).

Now after launch:

 curl http://localhost:3000/api/test 

output to the server console:

 Running INTERCEPT-REQUEST ACTUAL RESPONSE INTERCEPT-RESPONSE {"0":{"message":"testing"}} INTERCEPT-RESPONSE {"0":"{\"message\":\"testing\"}"} 

What will happen

As you can see, your handler is actually called once by your code, and the object is called the first (and only) argument. But then it is again called an object serialized in JSON. This is how res.send works internally - see below for more details. Since you put your intercept function in a valid response object, I assume that it calls itself with a JSON argument, and it doesn't even know that it is calling your function in the meantime.

How to avoid it

Try using an object serialized in JSON yourself:

 const express = require('express'); const app = express(); var router = express.Router(); var port = process.env.PORT || 3000; router.get('/test', function(req, res) { console.log('ACTUAL RESPONSE'); res.send(JSON.stringify({ message: "testing" })); }); app.use(function(req, res, next){ console.log("INTERCEPT-REQUEST"); const orig_send = res.send; res.send = function(arg) { console.log("INTERCEPT-RESPONSE", JSON.stringify(arguments)); orig_send.call(res, arg); }; next(); }); app.use("/api", router); app.listen(process.env.PORT || 3000, function () { console.log("Running"); }); 

Now it prints:

 Running INTERCEPT-REQUEST ACTUAL RESPONSE INTERCEPT-RESPONSE {"0":"{\"message\":\"testing\"}"} 

Call res.send only once.

Description

Now this is the code that processes the arguments of the res.json object:

  if (chunk === null) { chunk = ''; } else if (Buffer.isBuffer(chunk)) { if (!this.get('Content-Type')) { this.type('bin'); } } else { return this.json(chunk); } 

See: https://github.com/expressjs/express/blob/master/lib/response.js#L144-L154

You get the else branch, and it calls this.json() (which res.json() really is) with your argument.

But guess what - res.json() calls res.send() on this line:

 return this.send(body); 

See: https://github.com/expressjs/express/blob/master/lib/response.js#L250

Which call the interception function calls (the second time) before running real res.send() .

So the mystery is solved. :)

+3
source

Source: https://habr.com/ru/post/1262371/


All Articles