1. 问题
前几日,我在测试express框架的时候,构造了一个测试样例死活过不来,即便调试到测试框架superagent ,依然不对。最终发现是nodejs的bug,而且最新版本的nodejs已经"修复"了,导致我中间饶了几圈都没发现是nodejs的事,下面来重现问题流程。
2. 环境预备
- 安装一下
gnvm地址,后面需要控制一下版本(windows10 需要用管理员权限的cmd或者powershell) - 安装git环境(主要要使用
curl命令) - 摘抄如下代码
// main.js
var http = require('http')
var tmpObject = Object
tmpObject.prototype['love'] = 'express'
var server = http.createServer(function (_, res) {
res.setHeader("m", "w")
res.end()
})
server.listen(3010)
3.问题复现
- 安装nodejs (当前稳定版)版本
gnvm install 8.11.2
gnvm use 8.11.2
- 运行代码,
nodejs main.js - 使用
curl -i 127.0.0.1:3010命令,得到如下
$ curl -i 127.0.0.1:3010
HTTP/1.1 200 OK
m: w
e: x
Date: Fri, 18 May 2018 14:06:47 GMT
Connection: keep-alive
Content-Length: 0
能理解有一个头
m: w,但是e: x是从哪来的?
明明奇怪的改动只是Object.prototype.love='express'
4.再次测试
修改一下main.js的代码,注释掉res.setHeader("m", "w")试试看
// main.js
var http = require('http')
var tmpObject = Object
tmpObject.prototype['love'] = 'express'
var server = http.createServer(function (_, res) {
// res.setHeader("m", "w")
res.end()
})
server.listen(3010)
$ curl -i 127.0.0.1:3010
HTTP/1.1 200 OK
Date: Fri, 18 May 2018 14:30:01 GMT
Connection: keep-alive
Content-Length: 0
竟然没有了
5.解释
翻阅v8.11.2代码
_http_outgoing.js#L497
OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
...
if (!this[outHeadersKey])
this[outHeadersKey] = {};
const key = name.toLowerCase();
this[outHeadersKey][key] = [name, value];
...
};
那么3测试里面代码执行的时候,保存header的数据是这样的
this[outHeaderKey] = {
"m": ["m", "w"]
}
另外注意变量初始化this[outHeadersKey] = {},
那么this[outHeaderKey]的原型链指向Object.prototype
有了上面的认知,来看下res.end()做了哪些事,写一下调用链
_http_outgoing.end() => _http_server._implicitHeader() => _http_server.writeHead() => _http_outgoing._storeHeader()
看一下_http_server.writeHead(),_http_server#L202
headers = this[outHeadersKey];
this._storeHeader(statusLine, headers);
继续看一下_http_outgoing.storeHeader(),_http_server#L307
if (headers === this[outHeadersKey]) {
for (key in headers) {
var entry = headers[key];
field = entry[0];
value = entry[1];
...
}
1.当上述for in遍历到自定义res.setHeader("m", "w") 中的 "m":["m": "w"]
key=m,entry = [m, w]
则取出数据 field = m,value = w,没毛病
2.但当for in遍历到原型链的时候,key = 'love',entry = 'express'
那么field = entry[0] = 'e',value = entry[1] = 'x'
故而响应头中的 e:x 就是这么来的
6.小结
本质上是for in遍历到原型链,加上nodejs保存 outHeadersKey 的"奇怪"数组方式
才会导致发包过程中出现了一个难以理解的header
另外,对于for in来说,项目中通常采用hasOwnProperty来规避问题,但是新版本nodejs不是这样做的,下面是最新的nodejs这块代码
_http_outgoing.js#L121
const headers = this[outHeadersKey] = Object.create(null);
Object.create(null)会把创建出来的对象__proto__ 指向 null
for in 就不会遍历到了,可以使用gnvm use v10.1.0尝试一下,最新版本已经没有问题了