做者:chainhelen from 迅雷前端
本文讲述做者是如何定位发现 Node.js 存在的一处问题,Node.js 最新版本已经修复了该问题。本文主要分享定位问题的思路和方法,当你在开发当中遇到疑难问题的时候,不排除是依赖的技术和框架出现了问题,当你尝试找到并修复它,我相信不光能够收获到贡献代码的成就感,也会带来技术水平和信心的提高。node
前几日,我在测试express
框架的时候,构造了一个测试样例死活过不来,即使调试到测试框架superagent
,依然不对。最终发现是Node.js
的"问题",并且最新版本的Node.js
已经"修复"了,致使我中间饶了几圈都没发现是Node.js
的事,下面来重现问题流程。git
gnvm
地址,后面须要控制一下版本(windows10 须要用管理员权限的 cmd 或者 powershell)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)
复制代码
gnvm install 8.11.2
gnvm use 8.11.2
复制代码
Node.js 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'github
修改一下main.js
的代码,注释掉res.setHeader("m", "w")
试试看shell
// 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
复制代码
居然没有了express
翻阅v8.11.2
代码 _http_outgoing.js#L497windows
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
bash
有了上面的认知,来看下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
就是这么来的
本质上是for in
遍历到原型链,加上Node.js
保存 outHeadersKey
的"奇怪"数组方式
才会致使发包过程当中出现了一个难以理解的header
另外,对于for in
来讲,项目中一般采用hasOwnProperty
来规避问题,可是新版本Node.js
不是这样作的,下面是最新的Node.js
这块代码 _http_outgoing.js#L121
const headers = this[outHeadersKey] = Object.create(null);
复制代码
Object.create(null)
会把建立出来的对象__proto__
指向 null
for in
就不会遍历到了,能够使用gnvm use v10.1.0
尝试一下,最新版本已经没有问题了