歡迎來個人博客閱讀:《Node.js源碼解析-HTTP請求響應過程》html
在 Node.js 中,起一個 HTTP Server 很是簡單,只須要以下代碼便可:node
const http = require('http') http.createServer((req, res) => { res.end('Hello World\n') }).listen(3000)
$ curl localhost:3000 Hello World
對,就這麼簡單。由於 Node.js 已經把具體實現細節給封裝起來了,咱們只須要調用 http 模塊提供的方法便可git
那麼,一個請求是如何處理,而後響應的呢?讓咱們來看看源碼github
首先,讓咱們理一理思路緩存
_______ | | <== res request ==> | ? | |_______| ==> req /\ || http.createServer()
先調用 http.createServer()
生成一個 http.Server
對象 ( 黑盒 ) 來處理請求數據結構
每次收到請求,都先解析生成 req ( http.IncomingMessage
) 和 res ( http.ServerResponse
),而後交由用戶函數處理app
用戶函數調用 res.end()
來結束處理,響應請求curl
綜上,咱們的切入點有:異步
讓咱們先來看看 http.createServer()
// lib/http.js function createServer(requestListener) { return new Server(requestListener); } // lib/_http_server.js function Server(requestListener) { if (!(this instanceof Server)) return new Server(requestListener); net.Server.call(this, { allowHalfOpen: true }); if (requestListener) { this.on('request', requestListener); } this.on('connection', connectionListener); // ... }
http.createServer()
函數返回一個 http.Server
實例
該實例監聽了 request 和 connection 兩個事件
request 事件
:綁定 requestListener()
函數,req 和 res 準備好時觸發
connection 事件
:綁定 connectionListener()
函數,鏈接時觸發
用戶函數是 requestListener()
,所以,咱們須要知道 request 事件什麼時候觸發
// lib/_http_server.js function connectionListener(socket) { // ... // 從 parsers 中取一個 parser var parser = parsers.alloc(); parser.reinitialize(HTTPParser.REQUEST); parser.socket = socket; socket.parser = parser; // ... state.onData = socketOnData.bind(undefined, this, socket, parser, state); // ... socket.on('data', state.onData); // ... } function socketOnData(server, socket, parser, state, d) { // ... var ret = parser.execute(d); // ... }
當鏈接創建時,觸發 connnection 事件,執行 connectionListener()
這裏的 socket 正是鏈接的 socket 對象,給 socket 綁定 data 事件用來處理數據,處理數據用到的 parser 是從 parsers 中取出來的
data 事件觸發時,執行 socketOnData()
,最後調用 parser.execute()
來解析 HTTP 報文
值得一提的是 parsers 由一個叫作 FreeList ( wiki ) 的數據結構實現,其主要目的是複用 parser
// lib/internal/freelist.js class FreeList { constructor(name, max, ctor) { this.name = name; this.ctor = ctor; this.max = max; this.list = []; } alloc() { return this.list.length ? this.list.pop() : this.ctor.apply(this, arguments); } free(obj) { if (this.list.length < this.max) { this.list.push(obj); return true; } return false; } } module.exports = FreeList;
經過調用 parsers.alloc()
和 parsers.free(parser)
來獲取釋放 parser ( http 模塊中 max 爲 1000 )
既然,HTTP 報文是由 parser 來解析的,那麼,就讓咱們來看看 parser 是如何建立的吧
// lib/_http_common.js const binding = process.binding('http_parser'); const HTTPParser = binding.HTTPParser; // ... var parsers = new FreeList('parsers', 1000, function() { var parser = new HTTPParser(HTTPParser.REQUEST); parser._headers = []; parser._url = ''; parser._consumed = false; parser.socket = null; parser.incoming = null; parser.outgoing = null; parser[kOnHeaders] = parserOnHeaders; parser[kOnHeadersComplete] = parserOnHeadersComplete; parser[kOnBody] = parserOnBody; parser[kOnMessageComplete] = parserOnMessageComplete; parser[kOnExecute] = null; return parser; }); function parserOnHeadersComplete(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { // ... if (!upgrade) { skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive); } // ... } // lib/_http_server.js function connectionListener(socket) { // ... parser.onIncoming = parserOnIncoming.bind(undefined, this, socket, state); // ... }
parser 是由 http_parser
這個庫實現。不難看出,這裏的 parser 也是基於事件的
在解析過程當中,所經歷的事件:
kOnHeaders:不斷解析獲取的請求頭
kOnHeadersComplete:請求頭解析完畢
kOnBody:不斷解析獲取的請求體
kOnMessageComplete:請求體解析完畢
kOnExecute:一次解析完畢 ( 沒法一次性接收 HTTP 報文的狀況 )
當請求頭解析完畢時,對於非 upgrade 請求,能夠直接執行 parser.onIncoming()
,進行響應
// lib/_http_server.js // 生成 response,並觸發 request 事件 function parserOnIncoming(server, socket, state, req, keepAlive) { state.incoming.push(req); // ... var res = new ServerResponse(req); // ... if (socket._httpMessage) { state.outgoing.push(res); } else { res.assignSocket(socket); } // ... res.on('finish', resOnFinish.bind(undefined, req, res, socket, state, server)); // ... server.emit('request', req, res); // ... } function resOnFinish(req, res, socket, state, server) { // ... state.incoming.shift(); // ... res.detachSocket(socket); // ... var m = state.outgoing.shift(); if (m) { m.assignSocket(socket); } } }
能夠看出,在源碼中,對每個 socket,維護了 state.incoming
和 state.outgoing
兩個隊列,分別用於存儲 req 和 res
當 finish 事件觸發時,將 req 和 res 從隊列中移除
執行 parserOnIncoming()
時,最後會觸發 request 事件,來調用用戶函數
就拿最開始的例子來講:
const http = require('http') http.createServer((req, res) => { res.end('Hello World\n') }).listen(3000)
經過調用 res.end('Hello World\n')
來告訴 res,處理完成,能夠響應並結束請求
ServerResponse 繼承自 OutgoingMessage,res.end()
正是 OutgoingMessage.prototype.end()
// lib/_http_outgoing.js OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { // ... if (chunk) { // ... write_(this, chunk, encoding, null, true); } else if (!this._header) { this._contentLength = 0; // 生成響應頭 this._implicitHeader(); } // ... // 觸發 finish 事件 var finish = onFinish.bind(undefined, this); var ret; if (this._hasBody && this.chunkedEncoding) { // trailer 頭相關 ret = this._send('0\r\n' + this._trailer + '\r\n', 'latin1', finish); } else { // 保證必定會觸發 finish 事件 ret = this._send('', 'latin1', finish); } this.finished = true; // ... }; function write_(msg, chunk, encoding, callback, fromEnd) { // ... if (!msg._header) { msg._implicitHeader(); } // ... var len, ret; if (msg.chunkedEncoding) { // 響應大文件時,分塊傳輸 if (typeof chunk === 'string') len = Buffer.byteLength(chunk, encoding); else len = chunk.length; msg._send(len.toString(16), 'latin1', null); msg._send(crlf_buf, null, null); msg._send(chunk, encoding, null); ret = msg._send(crlf_buf, null, callback); } else { ret = msg._send(chunk, encoding, callback); } // ... }
執行函數時,若是給了 chunk,就先 write_(chunk)
,寫入 chunk。再 _send('', 'latin1', finish)
綁定 finish 函數。待寫入完成後,觸發 finish 事件,結束響應
在寫入 chunk 以前,必須確保 headers 已經生成,若是沒有則調用 _implicitHeader()
隱式生成 headers
// lib/_http_server.js ServerResponse.prototype._implicitHeader = function _implicitHeader() { this.writeHead(this.statusCode); }; ServerResponse.prototype.writeHead = writeHead; function writeHead(statusCode, reason, obj) { // ... // 將 obj 和 this[outHeadersKey] 中的全部 header 放到 headers 中 // ... var statusLine = 'HTTP/1.1 ' + statusCode + ' ' + this.statusMessage + CRLF; this._storeHeader(statusLine, headers); } // lib/_http_outgoing.js OutgoingMessage.prototype._storeHeader = _storeHeader; function _storeHeader(firstLine, headers) { // > GET /index.html HTTP/1.1\r\n // < HTTP/1.1 200 OK\r\n // ... // 校驗 header 並生成 HTTP 報文頭 // ... this._header = state.header + CRLF; this._headerSent = false; // ... }
headers 生成後,保存在 this._header
中,此時,響應報文頭部已經完成,只須要在響應體以前寫入 socket 便可
write_()
函數,內部也是調用 _send()
函數寫入數據
// lib/_http_outgoing.js OutgoingMessage.prototype._send = function _send(data, encoding, callback) { if (!this._headerSent) { // 將 headers 添加到 data 前面 if (typeof data === 'string' && (encoding === 'utf8' || encoding === 'latin1' || !encoding)) { data = this._header + data; } else { var header = this._header; if (this.output.length === 0) { this.output = [header]; this.outputEncodings = ['latin1']; this.outputCallbacks = [null]; } else { this.output.unshift(header); this.outputEncodings.unshift('latin1'); this.outputCallbacks.unshift(null); } this.outputSize += header.length; this._onPendingData(header.length); } this._headerSent = true; } return this._writeRaw(data, encoding, callback); }; OutgoingMessage.prototype._writeRaw = _writeRaw; function _writeRaw(data, encoding, callback) { const conn = this.connection; // ... if (conn && conn._httpMessage === this && conn.writable && !conn.destroyed) { if (this.output.length) { // output 中有緩存,先寫入緩存 this._flushOutput(conn); } else if (!data.length) { // 沒有則異步執行回調 if (typeof callback === 'function') { nextTick(this.socket[async_id_symbol], callback); } return true; } // 直接寫入 socket return conn.write(data, encoding, callback); } // 加入 output 緩存 this.output.push(data); this.outputEncodings.push(encoding); this.outputCallbacks.push(callback); this.outputSize += data.length; this._onPendingData(data.length); return false; }
對於 res.end()
來講,直接在 nextTick()
中觸發 finish 事件,結束響應
到這裏,咱們已經走完了一個請求從接收到響應的過程
除此以外,在 http 模塊中,還考慮了許多的實現細節,很是值得一看
參考: