記一次 Node.js 源碼分析

做者:chainhelen from 迅雷前端

本文講述做者是如何定位發現 Node.js 存在的一處問題,Node.js 最新版本已經修復了該問題。本文主要分享定位問題的思路和方法,當你在開發當中遇到疑難問題的時候,不排除是依賴的技術和框架出現了問題,當你嘗試找到並修復它,我相信不光能夠收穫到貢獻代碼的成就感,也會帶來技術水平和信心的提高。node

1. 問題

前幾日,我在測試express框架的時候,構造了一個測試樣例死活過不來,即使調試到測試框架superagent ,依然不對。最終發現是Node.js的"問題",並且最新版本的Node.js已經"修復"了,致使我中間饒了幾圈都沒發現是Node.js的事,下面來重現問題流程。git

2. 環境預備

  1. 安裝一下gnvm 地址,後面須要控制一下版本(windows10 須要用管理員權限的 cmd 或者 powershell)
  2. 安裝 git 環境(主要要使用curl命令)
  3. 摘抄以下代碼
// 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.問題復現

  1. 安裝 Node.js (當前穩定版)版本
gnvm install 8.11.2
gnvm use 8.11.2
複製代碼
  1. 運行代碼,Node.js main.js
  2. 使用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

4.再次測試

修改一下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

5.解釋

翻閱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.prototypebash

有了上面的認知,來看下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=mentry = [m, w] 則取出數據 field = mvalue = w,沒毛病

2.但當for in遍歷到原型鏈的時候,key = 'love'entry = 'express' 那麼field = entry[0] = 'e'value = entry[1] = 'x' 故而響應頭中的 e:x 就是這麼來的

6.小結

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

掃一掃關注迅雷前端公衆號

相關文章
相關標籤/搜索