精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

日韩一区网站| 国产永久免费高清在线观看视频| 午夜精品久久99蜜桃的功能介绍| 日韩欧美色综合网站| www.avtt| 狠狠色伊人亚洲综合网站l| 麻豆精品新av中文字幕| 欧美人成在线视频| www.中文字幕av | 日本高清视频精品| 国产精品免费在线视频| 高清精品xnxxcom| 欧美在线观看一区二区| 亚洲爆乳无码精品aaa片蜜桃| 香蕉视频网站在线| 麻豆中文一区二区| 91高清在线免费观看| 国精产品视频一二二区| 男人的天堂久久| 欧美精品乱码久久久久久按摩| 日韩中字在线观看| 国产激情小视频在线| 91片在线免费观看| 99久久综合狠狠综合久久止| 看黄色一级大片| 国产综合欧美| 久久综合久久美利坚合众国| 精品少妇一区二区三区免费观| 日韩一级淫片| 欧美日韩国产另类不卡| 日韩黄色片视频| 人交獸av完整版在线观看| 欧美国产视频在线| 欧美凹凸一区二区三区视频| 亚洲黄色一级大片| 久久国产精品露脸对白| 国产va免费精品高清在线| 久久久91视频| 91精品国产自产在线观看永久∴ | 国产一区深夜福利| 日本视频在线观看免费| 韩日在线一区| 久久97久久97精品免视看| 日韩一区二区三区四区视频| 九九热爱视频精品视频| 亚洲精品久久久久久下一站 | 中文字幕亚洲综合| 免费看黄色aaaaaa 片| 澳门精品久久国产| 日韩欧美中文字幕一区| 国产在线观看中文字幕| 亚洲精品自拍| 欧美人狂配大交3d怪物一区 | 国产黄色成人av| 国产精品香蕉av| 国产精品国产精品国产| 日本91福利区| 国产日韩中文字幕| 在线观看国产一区二区三区| 欧美aaa在线| 国产精品久久二区| 久草热在线观看| 蜜桃精品在线观看| 国产日韩亚洲欧美| 国产sm主人调教女m视频| 国产在线看一区| 91超碰在线免费观看| 国产黄色高清视频| 成人一区在线观看| 国产日韩久久| 奇米影视888狠狠狠777不卡| 久久精品视频在线看| 亚洲高清视频一区| 黄色av网站在线播放| 亚洲黄一区二区三区| 黄色三级中文字幕| 午夜欧美激情| 精品视频999| 午夜视频在线观| 精品国产导航| 中日韩午夜理伦电影免费| 又嫩又硬又黄又爽的视频| 久久久久久久久久久久久久| 九色成人免费视频| 亚洲黄色激情视频| 蜜桃av一区二区三区电影| 91香蕉嫩草影院入口| 空姐吹箫视频大全| 国产网红主播福利一区二区| 伊人久久大香线蕉午夜av| 青青青草视频在线| 色婷婷av一区二区| 青青草原播放器| 农村少妇一区二区三区四区五区| 精品一区二区三区三区| 免费精品在线视频| aⅴ色国产欧美| 国产精品丝袜久久久久久不卡| 国产伦理吴梦梦伦理| 成人免费视频caoporn| 欧美日韩一区二 | 午夜精品久久久久久久99热黄桃| av一区二区三区黑人| 亚洲春色综合另类校园电影| 白白色在线观看| 在线免费观看日本一区| japan高清日本乱xxxxx| 国产精品嫩草影院在线看| 久久成人这里只有精品| 欧美日韩一级黄色片| 国产福利一区在线| 日本不卡一二三区| 激情在线视频播放| 欧美日韩一区二区三区在线| 中国一级特黄录像播放| 日韩精品免费一区二区在线观看| 国内精品久久久久久中文字幕| 影音先锋国产资源| 99精品在线观看视频| 做爰高潮hd色即是空| 黄色亚洲网站| 亚洲成色777777在线观看影院| 精品一区二区6| 国产亚洲在线观看| 超碰97在线资源| 黄网页免费在线观看| 色婷婷综合久久久久中文一区二区| 性生活在线视频| 成人午夜国产| 国产成人涩涩涩视频在线观看 | 国产精品69久久久久水密桃| 视频一区视频二区视频三区高| 国产传媒在线观看| 精品国产乱码久久久久久免费| 一本在线免费视频| 久久国产精品久久w女人spa| 精品欧美一区二区久久久伦 | 7777精品视频| 亚洲精品一区二区三区四区 | 国产精品第108页| 九九热在线视频观看这里只有精品| 蜜桃av噜噜一区二区三区| 久久香蕉av| 日韩视频免费直播| 可以直接看的黄色网址| 国产一区二区不卡在线| 在线视频不卡国产| 激情小说亚洲| 色偷偷av一区二区三区| 中文字幕精品在线观看| 欧美激情一区三区| 999在线免费视频| 欧美丝袜一区| 国产欧美一区二区三区四区 | 美女av一区二区三区| 国产麻豆一精品一男同| 亚洲欧美激情一区二区| 亚洲男人天堂2021| 中文字幕av亚洲精品一部二部| 成人淫片在线看| av香蕉成人| 日韩精品一区国产麻豆| 久久久香蕉视频| www.亚洲在线| 黄色免费观看视频网站| 午夜欧洲一区| 国产精品久久久久久久久久久久久| 国产视频福利在线| 欧美福利视频导航| 久久久久人妻一区精品色欧美| 国产激情一区二区三区| 国产freexxxx性播放麻豆| 欧美午夜18电影| 国产精品a久久久久久| av中文天堂在线| 91精品欧美一区二区三区综合在| 私库av在线播放| 成人av第一页| 超碰影院在线观看| 久久久久久影院| 国产在线一区二区三区播放| 性国裸体高清亚洲| 中文字幕欧美视频在线| www.国产黄色| 欧美日韩国产综合新一区 | 亚洲欧美日韩国产一区二区三区| 中文字幕1区2区| 国产欧美高清| 在线不卡日本| 岛国精品一区| 国产精品第10页| 怡红院在线播放| 亚洲人成在线免费观看| 精品国产黄色片| 色综合天天综合网国产成人综合天| 三级黄色免费观看| 97精品久久久久中文字幕| 久久撸在线视频| 亚洲精选成人| 中国一级黄色录像| 亚洲人成伊人成综合图片| 91久久在线视频| 欧美成人ⅴideosxxxxx| 欧美精品在线免费观看| 国产香蕉在线| 一级欧美视频| 欧美猛男做受videos| 日韩在线高清| 91精品综合久久久久久| 日韩欧美亚洲国产| 国产精品免费看片| 91青草视频久久| 91社区在线高清| 日韩精品黄色网| 国产夫妻在线观看| 欧美色图免费看| 青青操免费在线视频| 亚洲人一二三区| www久久久久久久| 成人av在线一区二区三区| 小明看看成人免费视频| 久久精品综合| 免费看欧美黑人毛片| 999国产精品永久免费视频app| 精品欧美一区二区三区久久久| 国产精品亚洲四区在线观看| 国产精品久久久久福利| 国产夫妻在线播放| 九九九久久久久久| 免费av在线播放| 亚洲三级 欧美三级| 欧美熟妇另类久久久久久不卡| 51精品秘密在线观看| 在线观看av大片| 欧洲另类一二三四区| 国产无遮挡呻吟娇喘视频| 亚洲午夜一二三区视频| 青青草激情视频| 亚洲色图欧洲色图| 亚洲av无一区二区三区| 中文在线一区二区| 18精品爽国产三级网站| 欧美国产综合色视频| 国产成人免费观看网站| 欧美激情一区二区三区四区| 公肉吊粗大爽色翁浪妇视频| 国产日韩av一区二区| 91激情视频在线观看| 国产欧美一区二区三区鸳鸯浴| 国产特黄级aaaaa片免| 久久欧美中文字幕| 中文字字幕码一二三区| 久久亚区不卡日本| 中文字幕 日本| 91在线观看高清| 手机av免费看| 国产调教视频一区| 又色又爽的视频| 亚洲人妖av一区二区| 日韩精品123区| 亚洲一区欧美一区| 久草精品视频在线观看| 欧美视频一区二区三区…| 在线视频一区二区三区四区| 在线亚洲欧美专区二区| 伊人色综合久久久| 欧美一级爆毛片| 日本xxxxxwwwww| 亚洲少妇中文在线| 91在线网址| 欧美巨乳美女视频| 日韩精品av| 国产男人精品视频| 亚洲精品影片| 欧美日韩三区四区| 亚洲一区二区| 精品国产一区二区三区无码| 国产农村妇女精品一区二区| 爱情岛论坛亚洲首页入口章节| 激情亚洲综合在线| 在线播放第一页| 久久日一线二线三线suv| 久草福利资源在线| 午夜免费久久看| 欧美一级黄视频| 日韩视频免费直播| 久久视频www| 欧美成人合集magnet| 麻豆国产在线| 成人免费在线视频网站| 粉嫩av一区二区| 亚洲视频sss| 一区在线免费| 人人干人人干人人| 高清在线观看日韩| 国产一区二区三区四区在线| 亚洲宅男天堂在线观看无病毒| 无码人妻精品一区二区三区9厂| 欧美日韩国产综合久久| 欧美一级视频免费| 日韩在线视频免费观看高清中文 | 久久精品99久久香蕉国产色戒| 激情图片在线观看高清国产| 国产精品日韩欧美大师| 国产精品115| 一区二区三区的久久的视频| 一本不卡影院| 天天操精品视频| 久久精品一级爱片| www.天天色| 91精品国产品国语在线不卡| 伦理片一区二区三区| 欧美激情网友自拍| 日韩免费大片| 日本一区不卡| 一本色道久久综合| 欧美xxxx黑人| 国产精品高潮久久久久无| 国产在线观看黄色| 精品国产乱码久久久久久图片 | 香蕉成人app| 欧美精品一区二区三区在线看午夜 | av黄色在线免费观看| 五月天精品一区二区三区| av免费观看网址| 俺去了亚洲欧美日韩| 91亚洲精品| 欧美一区免费视频| 国产视频久久| 四虎永久免费观看| 亚洲免费电影在线| 一级黄色片视频| 中文字幕久久久av一区| 依依综合在线| 久久综合一区二区三区| 99视频精品免费观看| 韩国黄色一级片| 一区二区三区精密机械公司| 国产美女自慰在线观看| 久久精品国产久精国产一老狼| 国精品产品一区| 一级日韩一区在线观看| 日韩综合小视频| 一区二区三区久久久久| 色94色欧美sute亚洲13| 日韩欧美亚洲系列| 欧美中文字幕视频在线观看| 日韩大片在线免费观看| 可以在线看的av网站| av在线播放一区二区三区| 粉嫩aⅴ一区二区三区| 亚洲国产精品字幕| 精品人人视频| 欧美一二三区| 久久永久免费| 妖精视频在线观看免费| 欧美日韩精品一区二区三区 | 日韩美女久久久| 国产精品毛片一区二区在线看舒淇| 久久精品视频一| 秋霞一区二区三区| 欧美一级视频在线播放| 97久久超碰精品国产| 日韩精品一区二区亚洲av| 亚洲一区二区久久久| 欧美暴力调教| 国产精品av免费| 国产二区国产一区在线观看| 国产一级片久久| 国产视频久久久| 欧美日韩女优| 亚洲啊啊啊啊啊| 91啪亚洲精品| jizz国产在线| 久久久精品免费视频| 国产精品宾馆| 日本熟妇人妻中出| 亚洲欧洲av色图| 日本黄色不卡视频| 国产精品入口免费视| 中文字幕免费精品| 99久久人妻无码中文字幕系列| 日本福利一区二区| av在线app| 日本免费高清一区| 国产精品自拍网站| 天堂а√在线中文在线新版 | 超碰97av在线| 日韩欧美高清dvd碟片| 亚洲私拍视频| 日本一级淫片演员| 99麻豆久久久国产精品免费 | 不卡视频观看| 色之综合天天综合色天天棕色| 国产麻豆成人精品| 中文字幕免费观看| 欧美大片免费观看在线观看网站推荐 | 2020国产在线视频| 欧美日韩一区二区视频在线| 国产成人免费在线观看|