为什么res.send被调用两次?

我正在为快速路由器创build一个中间件,它将为每个请求和响应执行一些代码。 拦截一个请求很容易,有很多例子,但我还没有find任何东西拦截响应的优雅方法。 经过一番研究,我所想到的最好的方法就是replace响应对象的发送函数,如下面的代码片段所示:

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"); 

这种方法存在一个问题:出于某种原因,“INTERCEPT-RESPONSE”会在控制台中打印两次,这意味着res.send会被调用两次。

我可以在第一次调用res.locals时设置一个标志来避免处理响应两次,但是我想知道为什么res.send被调用两次?

更好的例子

试试这个代码来看看传递给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"); }); 

(当服务器正在监听时,我还更改了“正在运行”的打印,以便在服务器正在侦听之前打印您的代码&& ,但在这里并不重要)。

现在运行后:

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

服务器控制台上的输出是:

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

怎么了

正如你所看到的,你的处理程序实际上被你的代码调用了一次,一个对象作为第一个(也是唯一的)参数。 但是随后又被序列化为JSON的对象再次调用。 这是如何res.send内部工作 – 详情见下文。 既然你把你的截取函数放在实际的响应对象上,那么我猜它是用JSON参数调用自己的,而且它甚至不知道它同时调用了你的函数。

如何避免它

尝试与自己序列化为JSON的对象:

 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"); }); 

现在它打印:

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

只调用res.send一次。

说明

现在,这是处理res.json对象参数的代码:

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

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

你得到了else分支,它调用this.json() (这是真正的res.json() )与你的论点。

但是请猜一下: res.json() res.send()在这一行中调用了res.send()

 return this.send(body); 

请参阅: https : //github.com/expressjs/express/blob/master/lib/response.js#L250

在运行真正的res.send()之前调用了拦截函数(第二次res.send()

所以,神秘解决了。 🙂