精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Node.js HTTP Client 內存泄露問題

開發 前端
最近社區有一個開發者提交了一個關于 HTTP 模塊內存泄露的問題(issue),這個問題影響的是 Node.js HTTP 客戶端,但是是比較特殊的場景,一般不會出現,除非服務端惡意攻擊客戶端。

最近社區有一個開發者提交了一個關于 HTTP 模塊內存泄露的問題(issue),這個問題影響的是 Node.js HTTP 客戶端,但是是比較特殊的場景,一般不會出現,除非服務端惡意攻擊客戶端。最近提交了一個 PR 修復了這個問題,本文簡單介紹下這個問題和修復方案。

例子

先看下復現的代碼。

const http = require('http');
const gcTrackerMap = newWeakMap();
const gcTrackerTag = 'NODE_TEST_COMMON_GC_TRACKER';

function onGC(obj, gcListener) {
const async_hooks = require('async_hooks');
const onGcAsyncHook = async_hooks.createHook({
    init: function(id, type) {
      if (this.trackedId === undefined) {
        this.trackedId = id;
      }
    },
    destroy(id) {
      if (id === this.trackedId) {
        this.gcListener.ongc();
        onGcAsyncHook.disable();
      }
    },
  }).enable();
  onGcAsyncHook.gcListener = gcListener;

  gcTrackerMap.set(obj, new async_hooks.AsyncResource(gcTrackerTag));
  obj = null;
}


function createServer() {
const server = http.createServer((req, res) => {
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify({ hello: 'world' }));
    req.socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
  });

returnnewPromise((resolve) => {
    server.listen(0, () => {
      resolve(server);
    });
  });
}

asyncfunction main() {
const server = await createServer();
const req = http.get({
    port: server.address().port,
  }, (res) => {
    const chunks = [];
    res.on('data', (c) => chunks.push(c), 1);
    res.on('end', () => {
      console.log(Buffer.concat(chunks).toString('utf8'));
    });
  });
const timer = setInterval(global.gc, 300);
  onGC(req, {
    ongc: () => {
      clearInterval(timer);
      server.close();
    }
  });
}

main();

上面的代碼邏輯很簡單,首先發起一個 HTTP,然后拿到一個響應,特殊的地方在于服務器返回了兩個響應,從而導致了 request 對象不會被釋放,引起內存泄露問題。

HTTP 響應解析過程

下面來分析下原因,分析這個問題需要對 Node.js HTTP 協議解析過程有一些了解。簡單來說,Node.js 收到數據后,會調 parser.execute(data) 進行 HTTP 協議的解析。

// socket 收到數據時執行
function socketOnData(d) {
const socket = this;
const req = this._httpMessage;
const parser = this.parser;
// 解析 HTTP 響應
const ret = parser.execute(d);
// 響應解析完成,做一些清除操作,釋放相關對象內存
if (parser.incoming?.complete) {
    socket.removeListener('data', socketOnData);
    socket.removeListener('end', socketOnEnd);
    socket.removeListener('drain', ondrain);
    freeParser(parser, req, socket);
  }
}

function freeParser(parser, req, socket) {
if (parser) {
    cleanParser(parser);
    parser.remove();
    if (parsers.free(parser) === false) {
      // function closeParserInstance(parser) { parser.close(); }
      setImmediate(closeParserInstance, parser);
    } else {
      parser.free();
    }
  }
if (req) {
    req.parser = null;
  }
if (socket) {
    socket.parser = null;
  }
}

function cleanParser(parser) {
  parser.socket = null;
  parser.incoming = null;
  parser.outgoing = null;
  parser[kOnMessageBegin] = null;
  parser[kOnExecute] = null;
  parser[kOnTimeout] = null;
  parser.onIncoming = null;
}

在解析過程中會執行多個鉤子函數。

// 解析 header 時
const kOnHeaders = HTTPParser.kOnHeaders | 0;
// 解析 header 完成時
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
// 解析 HTTP body 時
const kOnBody = HTTPParser.kOnBody | 0;
// 解析完一個 HTTP 報文時
const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;

接著看 Node.js 在處理 HTTP 響應時,這些鉤子函數的邏輯。

解析到 header。

function parserOnHeaders(headers, url) {
  // Once we exceeded headers limit - stop collecting them
  if (this.maxHeaderPairs <= 0 ||
      this._headers.length < this.maxHeaderPairs) {
    this._headers.push(...headers);
  }
  this._url += url;
}

解析完 header。

function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
                                 url, statusCode, statusMessage, upgrade,
                                 shouldKeepAlive) {
  const parser = this;
  const { socket } = parser;
  const incoming = parser.incoming = new IncomingMessage(socket);
  return parser.onIncoming(incoming, shouldKeepAlive);
}

接著回調 onIncoming 函數。

function parserOnIncomingClient(res, shouldKeepAlive) {
const socket = this.socket;
const req = socket._httpMessage;

if (req.res) {
    // 收到了多個響應
    socket.destroy();
    return0;
  }
// 觸發 response 事件
if (req.aborted || !req.emit('response', res)) {
    // ...
  }
return0;  // No special treatment.
}

解析 HTTP 響應 body。

function parserOnBody(b) {
const stream = this.incoming;

// If the stream has already been removed, then drop it.
if (stream === null)
    return;

// 把 body push 到響應對象中
if (!stream._dumped) {
    const ret = stream.push(b);
    if (!ret)
      readStop(this.socket);
  }
}

解析完 HTTP 響應。

function parserOnMessageComplete() {
  const parser = this;
  const stream = parser.incoming;
  // stream 就是上面的 IncomingMessage 對象
  if (stream !== null) {
    // 標記響應對象解析完成
    stream.complete = true;
    // 標記流結束
    stream.push(null);
  }
}

分析問題

了解了大概的流程后看一下為啥會出現內存泄露問題,當通過 parser.execute(data) 解析響應時,因為服務器返回了兩個響應,第一次解析完 HTTP 響應 header 時執行以下代碼。

const incoming = parser.incoming = new IncomingMessage(socket);
return parser.onIncoming(incoming, shouldKeepAlive);

onIncoming 會觸發 response 事件,這是正常的流程,緊接著又解析完第二個響應的 header 時問題就來了,這時同樣會執行上面的代碼,并且注意 parser.incoming 指向了新的 IncomingMessage 對象,接著看這時 onIncoming 的邏輯。

// 已經收到了一個響應了,忽略并銷毀 socket
if (req.res) {
  socket.destroy();
  return 0;
}

Node.js 這里做了判斷,直接銷毀 socket 并返回,最終 parser.execute(data) 執行結束,相關代碼如下。

// 解析 HTTP 響應
const ret = parser.execute(d);
// 響應是否解析完成
if (parser.incoming?.complete) {
  // 做一些清除操作,釋放相關對象內存
  freeParser(parser, req, socket);
}

因為 parser.incoming 這時候指向的是第二個響應,其 complete 字段的值是 false,所以導致沒有執行清除操作,引起內存泄露。

修復方案

修復方案有兩個,一是在解析到第二個響應時,以下代碼返回 -1 表示解析出錯。

if (req.res) {
  socket.destroy();
  return -1;
}

但是這種方式有一個問題是,因為解析完第一個響應時已經觸發了 response 事件,然后這里如果又觸發 error 事件會比較奇怪,讓用戶側不好處理。第二種方案是忽略第二個響應。最終選擇的是第二種方案,改動如下。

if (req.res) {
  socket.destroy();
  if (socket.parser) {
      // Now, parser.incoming is pointed to the new IncomingMessage,
      // we need to rewrite it to the first one and skip all the pending IncomingMessage
      socket.parser.incoming = req.res;
      socket.parser.incoming[kSkipPendingData] = true;
    }
  return 0;
}

首先讓 parser.incoming 執行第一個響應,并且設置丟棄后續所有數據標記,然后在后續解析過程中忽略收到的數據,否則后續的數據會干擾第一個響應。

function parserOnBody(b) {
const stream = this.incoming;

if (stream === null || stream[kSkipPendingData])
    return;

if (!stream._dumped) {
    const ret = stream.push(b);
    if (!ret)
      readStop(this.socket);
  }
}

function parserOnMessageComplete() {
const parser = this;
const stream = parser.incoming;

if (stream !== null && !stream[kSkipPendingData]) {
    stream.complete = true;
    stream.push(null);
  }
}

1. issue:https://github.com/nodejs/node/issues/60025

2. PR:https://github.com/nodejs/node/pull/60062

責任編輯:武曉燕 來源: 編程雜技
相關推薦

2023-06-30 23:25:46

HTTP模塊內存

2025-01-08 08:47:44

Node.js內存泄露定時器

2017-03-20 13:43:51

Node.js內存泄漏

2017-03-19 16:40:28

漏洞Node.js內存泄漏

2014-09-12 10:35:09

Node.jsHTTP 206

2013-11-01 09:34:56

Node.js技術

2015-03-10 10:59:18

Node.js開發指南基礎介紹

2022-01-02 06:55:08

Node.js ObjectWrapAddon

2020-01-03 16:04:10

Node.js內存泄漏

2021-10-03 15:02:50

HTTPNodejs

2017-04-24 08:31:26

Node.jsExpress.jsHTTP

2022-06-23 06:34:56

Node.js子線程

2011-09-02 14:47:48

Node

2011-11-01 10:30:36

Node.js

2011-09-08 13:46:14

node.js

2011-09-09 14:23:13

Node.js

2012-10-24 14:56:30

IBMdw

2011-11-10 08:55:00

Node.js

2021-10-21 08:59:17

技術HTTP攻擊

2021-12-25 22:29:57

Node.js 微任務處理事件循環
點贊
收藏

51CTO技術棧公眾號

久久全国免费视频| 欧美一区二区三区四区在线观看 | aaa国产精品视频| 精品国产乱码久久久久久天美| 麻豆久久久av免费| 国产精品福利电影| 亚洲激情一区| 伊人av综合网| 日韩女优在线视频| 日韩视频网站在线观看| 国产精品久久久久一区| 国产精品一区二区在线观看| 色婷婷av国产精品| 久久精品亚洲欧美日韩精品中文字幕| 亚洲成人黄色在线| 久热精品在线播放| 久久青草伊人| 亚洲欧美区自拍先锋| 久久资源av| 丰满肉肉bbwwbbww| 麻豆精品在线播放| 日本乱人伦a精品| 国产精品老熟女一区二区| 国产探花在线精品| 亚洲精品97久久| 三级av免费看| 久久精品国产福利| 欧美性黄网官网| av网站手机在线观看| 国产剧情在线| 中文字幕第一区综合| 久久综合色一本| 人妻偷人精品一区二区三区| 国内精品伊人久久久久av影院| 国产精品 欧美在线| 国产精品久久久免费视频| 激情六月综合| 欧美成人剧情片在线观看| 日韩在线不卡av| 清纯唯美日韩| 伊人青青综合网站| 三级网站在线免费观看| 亚洲精品一区国产| 日韩欧美国产一区二区在线播放| 岛国av在线免费| jizz免费一区二区三区| 在线视频一区二区免费| 久久精品午夜福利| 免费日韩电影| 一本大道av一区二区在线播放 | 91九色丨porny丨极品女神| 日韩成人精品一区| 在线看日韩欧美| 久久美女免费视频| 欧美一区二区麻豆红桃视频| 亚洲亚裔videos黑人hd| 精品无码在线观看| 四虎国产精品免费观看| 久久精品视频亚洲| 久热这里有精品| 欧美激情在线| 久久久久久久久久婷婷| 国产午夜精品无码| 亚洲乱码视频| 欧美专区日韩视频| 中文字幕在线天堂| 美女脱光内衣内裤视频久久网站 | 一区二区在线免费| www.国产二区| av资源中文在线| 日韩欧美福利视频| 国产小视频精品| 国产亚洲高清在线观看| 亚洲成人激情在线| 九色porny自拍视频| 日韩黄色大片| 欧美高清第一页| av大片在线免费观看| 日韩精品久久久久久| 成人黄色av播放免费| 国产视频在线观看免费| 99视频有精品| 亚洲免费视频一区| 日本一本在线免费福利| 精品女同一区二区三区在线播放| 久久婷婷五月综合色国产香蕉| 伊人久久高清| 日韩视频免费直播| 免费成人深夜夜行p站| 欧美一区电影| 久久久久久香蕉网| 亚洲 国产 日韩 欧美| 国产在线播精品第三| 精品国产乱码久久久久久丨区2区| 头脑特工队2在线播放| 国产欧美日本一区视频| 国产亚洲精品久久久久久久| 忘忧草在线日韩www影院| 欧美日韩久久久久久| 激情综合激情五月| 日韩欧美高清| 91国产视频在线播放| 国产又粗又大又黄| 91麻豆国产福利在线观看| 亚洲黄色成人久久久| 丁香花视频在线观看| 欧美亚洲禁片免费| 水蜜桃av无码| 国产精品久久观看| 日韩av免费看| www.久久精品.com| 国产精品天天看| 国产91在线免费| 日韩欧美另类中文字幕| 夜夜嗨av一区二区三区四区 | 狠狠88综合久久久久综合网| 国产精品久久久久999| 性生活黄色大片| 国产精品婷婷午夜在线观看| 大肉大捧一进一出好爽视频| 嗯用力啊快一点好舒服小柔久久| 自拍视频国产精品| 久久久久女人精品毛片九一 | 欧洲中文字幕精品| 91av在线免费| 欧美色视频免费| 欧美好骚综合网| 欧美黄色片在线观看| 在线观看国产小视频| 91亚洲国产成人精品一区二区三 | 久久成人亚洲| 激情视频在线观看一区二区三区| 日本在线视频www鲁啊鲁| 欧美区一区二区三区| 蜜桃av免费看| 另类图片国产| 麻豆av福利av久久av| 色偷偷色偷偷色偷偷在线视频| 日韩片之四级片| 手机在线免费看毛片| 久久国产精品色| 亚洲综合首页| 日韩一区二区三免费高清在线观看| 亚洲人成网站999久久久综合| 日本三级一区二区| www.亚洲人| 激情深爱综合网| 果冻天美麻豆一区二区国产| 久久久亚洲天堂| 国模人体一区二区| 精品国产户外野外| 屁屁影院国产第一页| 日韩午夜在线| 鲁丝一区二区三区免费| 欧美日韩成人影院| 亚洲区在线播放| www.久久久久久久| 国产欧美日韩亚州综合| 国产成年人视频网站| 五月天激情综合网| 亚洲一区二区三区乱码aⅴ| 欧美激情黑人| 欧美一级片免费看| 久久久久成人精品无码| 99久久精品国产导航| 成人观看免费完整观看| 精品免费视频| 国产欧亚日韩视频| 50度灰在线| 亚洲国产黄色片| 欧美一区免费看| 国产精品乱码人人做人人爱| 欧美成人手机在线视频| 欧美私人啪啪vps| 国产一区二区无遮挡| 亚洲精品日产| 中文字幕亚洲欧美日韩在线不卡| 国产一区二区三区三州| 一区二区三区高清在线| 蜜臀av一区二区三区有限公司| 久久夜色精品| 特级毛片在线免费观看| 成人自拍在线| 日本久久久久久久| 菠萝菠萝蜜在线视频免费观看 | 国产欧美一区二区白浆黑人| 羞羞视频在线观看免费| 日韩精品一区二区三区第95| 中文字幕在线观看高清| 一区二区三区精品在线| b站大片免费直播| 国产真实乱子伦精品视频| 水蜜桃色314在线观看| jlzzjlzz亚洲女人| 成人中文字幕+乱码+中文字幕| 久久国产精品黑丝| 亚洲毛片在线| 欧美专区在线观看| 黄视频网站在线看| 精品无人国产偷自产在线| 国产尤物在线观看| 欧美日韩一区二区三区| 久久久久久国产精品久久| 国产日韩欧美视频在线观看| 高跟丝袜一区二区三区| 成人在线观看高清| 久久这里只有精品首页| 两女双腿交缠激烈磨豆腐| 久久亚洲风情| 久久久久久免费看| 亚洲成人三区| 日韩欧美视频一区二区| 久久精品色综合| 成人综合国产精品| 最新日韩一区| 91av在线免费观看视频| 影音先锋在线播放| 中文字幕最新精品| 伦理片一区二区三区| 精品国产精品一区二区夜夜嗨| 在线观看日批视频| 色一情一伦一子一伦一区| 国产精品第108页| 亚洲人成影院在线观看| 国产一区二区三区播放| 黄色大片在线看| 欧美成人性战久久| 国产免费黄色大片| 欧美日韩一区在线| 亚洲精品91天天久久人人| 天天色综合成人网| 国产精品theporn动漫| 悠悠色在线精品| 国产免费无码一区二区视频| 亚洲三级视频在线观看| 亚洲图片第一页| 国产日韩av一区| www.99热| 国产农村妇女毛片精品久久麻豆 | 欧美洲成人男女午夜视频| 啊啊啊久久久| 韩国视频理论视频久久| 波多野结衣中文字幕久久| 欧美日本亚洲视频| 亚洲羞羞网站| 久久99亚洲热视| 成人av影院在线观看| 久久久久成人精品| 白白色在线观看| 国内精品久久久| 激情aⅴ欧美一区二区欲海潮| 97久久精品国产| sis001欧美| 国产福利成人在线| h1515四虎成人| 国产在线精品自拍| 国产一区二区三区视频在线| 亚洲自拍在线观看| 高清精品xnxxcom| 精品综合在线| 少妇精品久久久一区二区三区| 日韩欧美国产二区| 大片网站久久| 中文字幕免费高| 欧美日韩视频| 春日野结衣av| 日本在线观看不卡视频| 91视频这里只有精品| 国产精品一区二区三区网站| 欧美丰满熟妇bbb久久久| 成av人片一区二区| 一区二区三区四区免费| 中文字幕免费在线观看视频一区| 欧美性x x x| 亚洲国产视频一区二区| 亚洲精品男人的天堂| 欧美午夜精品理论片a级按摩| 国产区精品在线| 日韩电影中文字幕| 自拍视频在线| 97精品国产97久久久久久| 欧美www.| 成人91视频| 精品中文字幕一区二区三区av| 亚洲三区在线观看| 最新成人av网站| 狠狠躁狠狠躁视频专区| 国产真实乱偷精品视频免| 国产精品久久久久久久无码| 中文字幕不卡的av| 久久一二三四区| 在线日韩一区二区| 亚洲av无码一区二区三区dv| 亚洲男人第一网站| 秋霞在线午夜| 国产精品h片在线播放| 综合久久成人| 亚洲不卡一卡2卡三卡4卡5卡精品| 欧美顶级大胆免费视频| 免费av观看网址| 国产一区二区精品久久99| www.中文字幕av| 亚洲一区成人在线| 亚洲在线视频播放| 精品视频中文字幕| 日本大片在线播放| 国产精品视频一区二区三区四 | 亚洲精品suv精品一区二区| 欧美天天影院| 国产va免费精品高清在线观看| 亚洲日本一区二区三区在线| 一区精品在线| 丝袜美腿成人在线| 国产精品无码在线| 一区二区三区国产精品| 国产又黄又爽视频| 亚洲亚裔videos黑人hd| 免费高潮视频95在线观看网站| 91美女片黄在线观| 欧美大片aaaa| 国产精品天天av精麻传媒| 成人av在线电影| 麻豆成人在线视频| 欧美一区二区三区播放老司机| youjizz在线播放| 日本精品视频网站| 欧美性生活一级片| 国产一区二区三区小说| 国产精品自拍毛片| 日本一级二级视频| 7777精品伊人久久久大香线蕉完整版| 九色在线视频蝌蚪| 热久久这里只有精品| 欧美日韩一区二区三区不卡视频| 性高湖久久久久久久久aaaaa| 狠狠色丁香久久婷婷综| 国产一区第一页| 欧美日韩电影一区| av在线电影院| 国产精品视频白浆免费视频| 欧美午夜精彩| www.精品在线| 国产精品理论在线观看| 国产又粗又长又黄| 精品国模在线视频| 国产精品一区二区三区四区在线观看 | 亚洲欧洲无码一区二区三区| 中文字幕免费高清网站| 亚洲天堂开心观看| 桃子视频成人app| 污视频在线免费观看一区二区三区| 日韩成人午夜精品| 国产99在线 | 亚洲| 欧美日韩国产高清一区二区三区 | 三级外国片在线观看视频| 国产美女被下药99| 91精品国偷自产在线电影| 久久综合在线观看| 亚洲一区成人在线| 青青草视频免费在线观看| 国产精品av在线播放| 国产精品x453.com| 国产女主播在线播放| 黑丝美女久久久| 91在线网址| 97超碰资源| 国产精品嫩草99av在线| 色哟哟精品观看| 5566中文字幕一区二区电影| 欧美人体视频xxxxx| 久久伦理网站| 美女脱光内衣内裤视频久久影院| 欧美性猛交xxxxx少妇| 亚洲国产天堂久久综合| 免费观看成人性生生活片| 亚洲制服中文| 不卡电影免费在线播放一区| 亚洲天堂视频网站| www国产精品视频| 国产精品国产| 色播五月激情五月| 亚洲国产精品久久久男人的天堂| 久久天堂电影| 不卡一区二区三区视频| 日日夜夜免费精品| 免费人成年激情视频在线观看 | 日本www在线| 国内精品一区二区| 久久精品久久综合| 久久精品亚洲无码| 在线观看国产欧美| ccyy激情综合| 人人干人人干人人| 亚洲福利一区二区三区| www亚洲人| 国内精品一区二区| 国产乱淫av一区二区三区| 手机在线看片1024| 久久久久久国产精品三级玉女聊斋| 日韩欧美不卡|