国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

通過源碼解析 Node.js 中一個 HTTP 請求到響應(yīng)的歷程

qqlcbb / 939人閱讀

摘要:請求的來到在中,若要收到一個請求,首先需要創(chuàng)建一個類的實例,然后監(jiān)聽它的事件。不斷解析推入的請求體數(shù)據(jù)。處理具體解析完畢的請求。比較繞,以一個簡化的圖示來總結(jié)這部分邏輯響應(yīng)該請求到了響應(yīng)時,事情已經(jīng)簡單許多了,傳入的已經(jīng)獲取到了。

如果大家使用 Node.js 寫過 web 應(yīng)用,那么你一定使用過 http 模塊。在 Node.js 中,起一個 HTTP server 十分簡單,短短數(shù)行即可:

"use stirct"
const { createServer } = require("http")

createServer(function (req, res) {
  res.writeHead(200, { "Content-Type": "text/plain" })
  res.end("Hello World
")
})
.listen(3000, function () { console.log("Listening on port 3000") })
$ curl localhost:3000
Hello World

就這么簡單,因為 Node.js 把許多細(xì)節(jié)都已在源碼中封裝好了,主要代碼在 lib/_http_*.js 這些文件中,現(xiàn)在就讓我們照著上述代碼,看看從一個 HTTP 請求的到來直到響應(yīng),Node.js 都為我們在源碼層做了些什么。

HTTP 請求的來到

在 Node.js 中,若要收到一個 HTTP 請求,首先需要創(chuàng)建一個 http.Server 類的實例,然后監(jiān)聽它的 request 事件。由于 HTTP 協(xié)議屬于應(yīng)用層,在下層的傳輸層通常使用的是 TCP 協(xié)議,所以 net.Server 類正是 http.Server 類的父類。具體的 HTTP 相關(guān)的部分,是通過監(jiān)聽 net.Server 類實例的 connection 事件封裝的:

// lib/_http_server.js
// ...

function Server(requestListener) {
  if (!(this instanceof Server)) return new Server(requestListener);
  net.Server.call(this, { allowHalfOpen: true });

  if (requestListener) {
    this.addListener("request", requestListener);
  }

  // ...
  this.addListener("connection", connectionListener);

  // ...
}
util.inherits(Server, net.Server);

這時,則需要一個 HTTP parser 來解析通過 TCP 傳輸過來的數(shù)據(jù):

// lib/_http_server.js
const parsers = common.parsers;
// ...

function connectionListener(socket) {
  // ...
  var parser = parsers.alloc();
  parser.reinitialize(HTTPParser.REQUEST);
  parser.socket = socket;
  socket.parser = parser;
  parser.incoming = null;
  // ...
}

值得一提的是,parser 是從一個“池”中獲取的,這個“池”使用了一種叫做 free list(wiki)的數(shù)據(jù)結(jié)構(gòu),實現(xiàn)很簡單,個人覺得是為了盡可能的對 parser 進(jìn)行重用,并避免了不斷調(diào)用構(gòu)造函數(shù)的消耗,且設(shè)有數(shù)量上限(http 模塊中為 1000):

// lib/freelist.js
"use strict";

exports.FreeList = function(name, max, constructor) {
  this.name = name;
  this.constructor = constructor;
  this.max = max;
  this.list = [];
};


exports.FreeList.prototype.alloc = function() {
  return this.list.length ? this.list.pop() :
                            this.constructor.apply(this, arguments);
};


exports.FreeList.prototype.free = function(obj) {
  if (this.list.length < this.max) {
    this.list.push(obj);
    return true;
  }
  return false;
};

由于數(shù)據(jù)是從 TCP 不斷推入的,所以這里的 parser 也是基于事件的,很符合 Node.js 的核心思想。使用的是 http-parser 這個庫:

// lib/_http_common.js
// ...
const binding = process.binding("http_parser");
const HTTPParser = binding.HTTPParser;
const FreeList = require("internal/freelist").FreeList;
// ...

var parsers = new FreeList("parsers", 1000, function() {
  var parser = new HTTPParser(HTTPParser.REQUEST);
  // ...
  parser[kOnHeaders] = parserOnHeaders;
  parser[kOnHeadersComplete] = parserOnHeadersComplete; 
  parser[kOnBody] = parserOnBody; 
  parser[kOnMessageComplete] = parserOnMessageComplete;
  parser[kOnExecute] = null; 

  return parser;
});
exports.parsers = parsers;

// lib/_http_server.js
// ...

function connectionListener(socket) {
  parser.onIncoming = parserOnIncoming;
}

所以一個完整的 HTTP 請求從接收到完全解析,會挨個經(jīng)歷 parser 上的如下事件監(jiān)聽器:

parserOnHeaders:不斷解析推入的請求頭數(shù)據(jù)。

parserOnHeadersComplete:請求頭解析完畢,構(gòu)造 header 對象,為請求體創(chuàng)建 http.IncomingMessage 實例。

parserOnBody:不斷解析推入的請求體數(shù)據(jù)。

parserOnExecute:請求體解析完畢,檢查解析是否報錯,若報錯,直接觸發(fā) clientError 事件。若請求為 CONNECT 方法,或帶有 Upgrade 頭,則直接觸發(fā) connectupgrade 事件。

parserOnIncoming:處理具體解析完畢的請求。

所以接下來,我們的關(guān)注點自然是 parserOnIncoming 這個監(jiān)聽器,正是這里完成了最終 request 事件的觸發(fā),關(guān)鍵步驟代碼如下:

// lib/_http_server.js
// ...

function connectionListener(socket) {
  var outgoing = [];
  var incoming = [];
  // ...
  
  function parserOnIncoming(req, shouldKeepAlive) {
    incoming.push(req);
    // ...
    var res = new ServerResponse(req);
    
    if (socket._httpMessage) { // 這里判斷若為真,則說明 socket 正在被隊列中之前的 ServerResponse 實例占用
      outgoing.push(res);
    } else {
      res.assignSocket(socket);
    }
    
    res.on("finish", resOnFinish);
    function resOnFinish() {
      incoming.shift();
      // ...
      var m = outgoing.shift();
      if (m) {
        m.assignSocket(socket);
      }
    }
    // ...
    self.emit("request", req, res);
  }
}

可以看出,對于同一個 socket 發(fā)來的請求,源碼中分別維護(hù)了兩個隊列,用于緩沖 IncomingMessage 實例和對應(yīng)的 ServerResponse 實例。先來的 ServerResponse 實例先占用 socket ,監(jiān)聽其 finish 事件,從各自隊列中釋放該 ServerResponse 實例和對應(yīng)的 IncomingMessage 實例。

比較繞,以一個簡化的圖示來總結(jié)這部分邏輯:

響應(yīng)該 HTTP 請求

到了響應(yīng)時,事情已經(jīng)簡單許多了,傳入的 ServerResponse 已經(jīng)獲取到了 socket。http.ServerResponse 繼承于一個內(nèi)部類 http.OutgoingMessage,當(dāng)我們調(diào)用 ServerResponse#writeHead 時,Node.js 為我們拼湊好了頭字符串,并緩存在 ServerResponse 實例內(nèi)部的 _header 屬性中:

// lib/_http_outgoing.js
// ...

OutgoingMessage.prototype._storeHeader = function(firstLine, headers) {
  // ...
  if (headers) {
    var keys = Object.keys(headers);
    var isArray = Array.isArray(headers);
    var field, value;

    for (var i = 0, l = keys.length; i < l; i++) {
      var key = keys[i];
      if (isArray) {
        field = headers[key][0];
        value = headers[key][1];
      } else {
        field = key;
        value = headers[key];
      }

      if (Array.isArray(value)) {
        for (var j = 0; j < value.length; j++) {
          storeHeader(this, state, field, value[j]);
        }
      } else {
        storeHeader(this, state, field, value);
      }
    }
  }
  // ...
  this._header = state.messageHeader + CRLF; 
}

緊接著在調(diào)用 ServerResponse#end 時,將數(shù)據(jù)拼湊在頭字符串后,添加對應(yīng)的尾部,推入 TCP ,具體的寫入操作在內(nèi)部方法 ServerResponse#_writeRaw 中:

// lib/_http_outgoing.js
// ...

OutgoingMessage.prototype.end = function(data, encoding, callback) {
  // ...
  if (this.connection && data)
    this.connection.cork();
    
  var ret;
  if (data) {
    this.write(data, encoding);
  }
  
  if (this._hasBody && this.chunkedEncoding) {
    ret = this._send("0
" + this._trailer + "
", "binary", finish);
  } else {
    ret = this._send("", "binary", finish);
  }
  
  if (this.connection && data)
    this.connection.uncork();
    
  // ...
  return ret;
}

OutgoingMessage.prototype._writeRaw = function(data, encoding, callback) {
  if (typeof encoding === "function") {
    callback = encoding;
    encoding = null;
  }

  var connection = this.connection;
  // ...
  return connection.write(data, encoding, callback);
};
最后

到這,一個請求就已經(jīng)通過 TCP ,發(fā)回給客戶端了。其實本文中,只涉及到了一條主線進(jìn)行解析,源碼中還考慮了更多的情況,如超時,socket 被占用時的緩存,特殊頭,上游突然出現(xiàn)問題,更高效的已寫頭的查詢等等。非常值得一讀。

參考:

https://github.com/nodejs/node/blob/master/lib/_http_common.js

https://github.com/nodejs/node/blob/master/lib/_http_outgoing.js

https://github.com/nodejs/node/blob/master/lib/_http_server.js

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/79356.html

相關(guān)文章

  • 最近學(xué)前后端分離知識

    摘要:文本已收錄至我的倉庫,歡迎前后端分離這個詞相信大家都聽過,不知道大家是怎么理解的呢。流下不學(xué)無術(shù)的淚水目前我了解到的前后端分離,首先部署是分離的至少不會跟綁定在一起部署接口只返回數(shù)據(jù)關(guān)于前端這幾大框架這幾個我都是沒有寫過的,所以也就不多了。 前言 只有光頭才能變強(qiáng)。文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y ...

    MoAir 評論0 收藏0
  • Node.js 系列:原生 Node.js 應(yīng)用

    摘要:原生應(yīng)用是一個基于引擎的運(yùn)行環(huán)境使用了一個事件驅(qū)動非阻塞式的模型,使其輕量又高效的包管理器,是全球最大的開源庫生態(tài)系統(tǒng)本文主要介紹構(gòu)建一個應(yīng)用的基本步驟和模塊,并假定你已經(jīng)對有一定的了解本文引用部分代碼作為例子,如果希望參看全部源碼,歡迎去 原生 Node.js 應(yīng)用 Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運(yùn)行環(huán)境Node.js 使用了一個事件驅(qū)...

    Ocean 評論0 收藏0
  • node學(xué)習(xí)之路(一)—— 網(wǎng)絡(luò)請求

    摘要:域套接字使用或指定請求方法的字符串。請求路徑包含非法字符時拋出異常。保持資源池周圍的套接字在未來被用于其它請求。默認(rèn)值為當(dāng)使用的時候,通過正在保持活動的套接字發(fā)送包的頻繁程度。 文章來源:小青年原創(chuàng)發(fā)布時間:2016-09-29關(guān)鍵詞:JavaScript,nodejs,http,url ,Query String,爬蟲轉(zhuǎn)載需標(biāo)注本文原始地址: http://zhaomenghuan....

    bovenson 評論0 收藏0
  • 走進(jìn)Node.jsHTTP實現(xiàn)分析

    摘要:事實上,協(xié)議確實是基于協(xié)議實現(xiàn)的。的可選參數(shù)用于監(jiān)聽事件另外,它也監(jiān)聽事件,只不過回調(diào)函數(shù)是自己實現(xiàn)的。并且會把本次連接的套接字文件描述符封裝成對象,作為事件的參數(shù)。過載保護(hù)理論上,允許的同時連接數(shù)只與進(jìn)程可以打開的文件描述符上限有關(guān)。 作者:正龍(滬江Web前端開發(fā)工程師)本文為原創(chuàng)文章,轉(zhuǎn)載請注明作者及出處 上文走進(jìn)Node.js啟動過程中我們算是成功入門了。既然Node.js的強(qiáng)...

    April 評論0 收藏0
  • Koa源碼解析

    摘要:若大于時,將賦予,此時與相等。通過源碼分析,我們知道了的核心思想建立于中間件機(jī)制,它是一個設(shè)計十分簡潔巧妙的框架,擴(kuò)展性極強(qiáng),就是建立于之上的上層框架。 Koa是一款設(shè)計優(yōu)雅的輕量級Node.js框架,它主要提供了一套巧妙的中間件機(jī)制與簡練的API封裝,因此源碼閱讀起來也十分輕松,不論你從事前端或是后端研發(fā),相信都會有所收獲。 目錄結(jié)構(gòu) 首先將源碼下載到本地,可以看到Koa的源碼只包含...

    CarterLi 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<