做者: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
嘗試一下,最新版本已經沒有問題了