国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

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

2015-03-10 10:59:18

Node.js開發指南基礎介紹

2013-11-01 09:34:56

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-08 13:46:14

node.js

2011-11-01 10:30:36

Node.js

2011-09-02 14:47:48

Node

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技術棧公眾號

亚洲永久av| 成人免费小视频| 久久99国产精品久久99大师| 亚洲国产wwwccc36天堂| 日韩欧美激情一区二区| xxxx日韩| 亚洲激情视频在线| 最近2018中文字幕免费在线视频| 蜜臀a∨国产成人精品| 国产精品黄视频| 91成人在线网站| 亚洲国内精品视频| av在线播放免费| 国产精品每日更新| 91午夜在线观看| 免费日韩av片| 91久久国产精品91久久性色| 美女精品视频在线| 日韩激情视频在线| 香港伦理在线| 欧美日韩中文字幕在线视频| 久草福利资源在线视频| 国产欧美一区二区精品久导航| 国产91av视频在线观看| 亚洲一区欧美激情| av观看久久| 亚洲有吗中文字幕| 日本aa大片在线播放免费看| 国产一区二区剧情av在线| 高清免费日韩| 国产精品久久观看| 国产精品久久久久久av福利软件 | 国产视频一区二区不卡| 自拍视频一区| 韩国精品美女www爽爽爽视频| 日韩高清不卡| 亚洲黄色www| 欧美激情办公室videoshd| 亚洲成人av资源| 91传媒在线观看| 国产精品久久久久aaaa| 女性隐私黄www网站视频| 成人美女在线观看| 丰满少妇大力进入| 不卡一卡二卡三乱码免费网站| 伊人久久大香线蕉午夜av| 日韩国产欧美三级| 日韩一区国产在线观看| 麻豆中文一区二区| 91精品一区二区三区四区| 国产福利精品导航| 免费看一级大黄情大片| 久久久久国产成人精品亚洲午夜| 国产成人亚洲精品无码h在线| 26uuu色噜噜精品一区二区| 黑森林福利视频导航| 久久久综合视频| 亚洲天堂2018av| 亚洲一区视频在线| www.黄在线观看| 亚洲国产成人爱av在线播放| 日韩一区二区三区免费| 欧美成人小视频| 香蕉久久夜色精品国产使用方法| 国产精品日韩欧美| 黄色亚洲精品| 一区二区精品视频| 99久久国产综合精品麻豆| 激情五月婷婷久久| 亚洲6080在线| 性欧美videos高清hd4k| 亚洲欧美成人精品| 99久久99九九99九九九| 国产成人精品一区| 亚洲高清网站| 欧美一区二区三区综合| 国产精品全国免费观看高清 | 欧美大尺度做爰床戏| 国产日韩欧美电影| 欧美福利网站| 91精选在线观看| 91另类视频| 国产成人啪精品视频免费网| 91久久综合| 国产原创中文在线观看 | 视频一区欧美| 国产日本一区二区三区| 国产精品中文字幕日韩精品 | 日韩黄色三级| 国产成人精品日本亚洲| 亚洲国产高清一区二区三区| 欧美一区二区激情| 狠狠色噜噜狠狠狠狠97| xxx欧美xxx| 国产精品偷伦视频免费观看国产 | 理论片在线观看理伦片| 精品国产伦理网| 99re8这里有精品热视频8在线| 国产精品一二三在线| 另类中文字幕网| 日韩av片网站| 欧美日韩免费一区二区三区| www一区二区三区| 亚洲综合在线播放| 97久久精品人人澡人人爽| 欧美黄色小说| 色黄久久久久久| 欧美日韩国产色综合一二三四| 日本少妇高潮喷水视频| 日本久久电影网| 99亚洲男女激情在线观看| 高清视频一区| 久久嫩草精品久久久久| 免费在线你懂的| 欧美最近摘花xxxx摘花| 国产在线精品一区二区不卡了| 成人精品3d动漫| 亚洲美女在线视频| 欧美激情五月| 成人网18免费网站在线| 亚洲级视频在线观看免费1级| 日韩久久不卡| 日韩欧美手机在线| 亚洲午夜激情网页| 久久er热在这里只有精品66| 成人女保姆的销魂服务| 欧美亚洲动漫精品| 亚洲人成在线播放| 国内精品久久久久久久97牛牛| 自拍偷拍99| 欧美性xxxx极品高清hd直播| 日韩视频一二区| 伊人狠狠色丁香综合尤物| 色成人在线视频| 天天久久夜夜| 日本a级片免费观看| 亚洲激情视频在线播放| 亚洲视屏一区| 欧美日夜夜逼| 欧美激情精品久久久久久黑人| 日韩成人免费看| 69视频在线| 成人在线观看av| 欧美性高跟鞋xxxxhd| 欧美精品一区二区三区中文字幕| 中国丰满人妻videoshd| 伊人久久精品视频| 国产久卡久卡久卡久卡视频精品| 好吊日视频在线观看| 成人在线免费观看视视频| 一区二区三区中文字幕精品精品 | 国产www精品| 国产午夜亚洲精品不卡| 另类中文字幕国产精品| 亚洲最新在线| 日韩欧美国产高清| 99国产一区| wwwxxx在线观看| 成人在线视频网址| 第一福利永久视频精品| 欧美手机在线| 久久白虎精品| 国产热re99久久6国产精品| 亚洲黄色片在线观看| 美女久久99| 最新在线你懂的| 91在线色戒在线| 91久久一区二区| 日韩一级精品| 狂野欧美性猛交xxxxx视频| 亚洲欧美精品| 精品亚洲一区二区| 成人精品免费看| 精品国产亚洲一区二区三区在线| 国产l精品国产亚洲区久久| 欧美成人免费一级人片100| 国产精品久久久久国产精品日日| 亚洲丝袜啪啪| 你懂的视频在线| 久久久综合香蕉尹人综合网| 精品国产凹凸成av人网站| 国产精品一区二区不卡| 日韩免费成人| 神马午夜dy888| 成人在线免费观看视视频| 欧美日韩和欧美的一区二区| 蜜臀a∨国产成人精品| 日韩深夜福利网站| 国产黄色av免费看| 亚洲自拍偷拍福利| 精品区一区二区| 国产91精品一区二区麻豆亚洲| 日本成人精品| 亚洲欧洲成人| 欧洲视频一区二区三区| 日韩一区视频在线| 亚洲永久精品国产| 先锋亚洲精品| 经典三级久久| 亚洲国产资源|