1. 程式人生 > >http server原始碼解析

http server原始碼解析

本文主要過下http生成服務和處理請求的主要流程,其他功能並未涉及。 ## 使用例子 ```js const http = require('http'); http.createServer((req, res) => { res.end('hello word'); }).listen(8080); ``` 例子中從生成服務,到接收請求,最後響應請求,其中主要的工作有4部分,分別是: - 呼叫`http.createServer`來生成一個服務 - 呼叫`listen`函式監聽埠 - 接收請求,生成`req`和`res`物件 - 執行業務函式,執行`res.end`響應請求 ## http.createServer和listen ```js // lib/http.js function createServer(opts, requestListener) { return new Server(opts, requestListener); } // lib/_http_server.js function Server(options, requestListener) { if (typeof options === 'function') { requestListener = options; options = {}; } // ... if (requestListener) { // 當req和res物件都生成好以後,就會觸發request事件,讓業務函式對請求進行處理 this.on('request', requestListener); } // connection事件可以在net Server類中看到,當三次握手完成後,就會觸發這個事件 this.on('connection', connectionListener); } ObjectSetPrototypeOf(Server.prototype, net.Server.prototype); ObjectSetPrototypeOf(Server, net.Server); function connectionListener(socket) { // 這裡就是執行connectionListenerInternal函式並傳入this和socket引數 defaultTriggerAsyncIdScope( getOrSetAsyncId(socket), connectionListenerInternal, this, socket ); } // connection事件觸發後的回撥函式,這個函式將在“解析生成req、res物件”板塊進行講解 function connectionListenerInternal(server, socket) { // ... } ``` 呼叫`http.createServer`函式時,會返回一個`Server`例項,`Server`是從`net Server`類繼承而來的。因此,`http Server`例項也就具備監聽埠生成服務,與客戶端通訊的能力。前面例子中呼叫的`listen`函式,實際上就是`net Server`中的`listen`。 在例項`Server`物件的過程中,會分別監聽`request`和`connection`這兩個事件。 - `connection`:這裡監聽的就是`net`中的`connection`事件,當客戶端發起請求,TCP三次握手連線成功時,服務端就會觸發`connection`事件。`connection`事件的回撥函式`connectionListenerInternal`將在下一個板塊進行講解。 - `request`:當`req`和`res`物件都初始成功以後,就會發布`request`事件,前面程式碼中我們可以看到`request`事件的回撥函式`requestListener`就是開發者呼叫`http.createServer`時傳入的回撥函式,這個回撥函式會接收`req`和`res`兩個物件。 ## 生成req、res物件 當客戶端TCP請求與服務端連線成功後,服務端就會觸發`connection`事件,此時就會例項一個[http-parser](https://github.com/nodejs/http-parser)用來解析客戶端請求,當客戶端資料解析成功後,就會生成一個`req`物件,接下來我們先來看下`req`物件生成過程。 ```js // lib/_http_server.js function Server(options, requestListener) { // ... // 客戶端與服務端三次握手完成,觸發connection事件 this.on('connection', connectionListener); } function connectionListener(socket) { // 這裡就是執行connectionListenerInternal函式並傳入this和socket引數 defaultTriggerAsyncIdScope( getOrSetAsyncId(socket), connectionListenerInternal, this, socket ); } /** * @param {http Server} server * @param {net Socket} socket */ function connectionListenerInternal(server, socket) { // ... // parsers.alloc函式執行會使用返回一個free list分配的HTTPParser物件 const parser = parsers.alloc(); // 請求解析器初始化工作 parser.initialize( HTTPParser.REQUEST, new HTTPServerAsyncResource('HTTPINCOMINGMESSAGE', socket), server.maxHeaderSize || 0, server.insecureHTTPParser === undefined ? isLenient() : server.insecureHTTPParser, server.headersTimeout || 0, ); parser.socket = socket; socket.parser = parser; // ... } // lib/_http_common.js const parsers = new FreeList('parsers', 1000, function parsersCb() { // 這裡使用http-parser庫來作為請求解析器 const parser = new HTTPParser(); cleanParser(parser); // ... return parser; }); ``` `http Server`中使用[http-parser](https://github.com/nodejs/http-parser)例項來作為客戶端請求的解析器。值得注意的是,這裡使用了[free list](https://zh.wikipedia.org/wiki/%E8%87%AA%E7%94%B1%E8%A1%A8)資料結構來分配`parser`物件。 ```js // 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 > 0 ? this.list.pop() : // 這裡的ctor是例項FreeList物件時,傳入的統一新增物件的方法 ReflectApply(this.ctor, this, arguments); } // 物件用完,釋放物件 free(obj) { if (this.list.length < this.max) { this.list.push(obj); return true; } return false; } } ``` 這部分運用到[free list](https://zh.wikipedia.org/wiki/%E8%87%AA%E7%94%B1%E8%A1%A8)資料結構。使用該資料結構目的是減少物件新建銷燬所帶來的效能消耗,它會維護一個長度固定的佇列,佇列中的所有物件大小都相同。當需要使用物件的時候,會優先從佇列中獲取空閒的物件,如果佇列中已經沒有可用的物件,就會新建一個與佇列中存放的物件大小相同的物件,供程式使用。物件使用完後,不會直接銷燬,而是會將物件壓入佇列中,直到後面被推出使用。 瞭解`free list`後,我們繼續來看下客戶端請求的解析。 ```js // lib/_http_common.js const parsers = new FreeList('parsers', 1000, function parsersCb() { const parser = new HTTPParser(); cleanParser(parser); // 為這些事件繫結回撥函式 parser[kOnHeaders] = parserOnHeaders; parser[kOnHeadersComplete] = parserOnHeadersComplete; parser[kOnBody] = parserOnBody; parser[kOnMessageComplete] = parserOnMessageComplete; return parser; }); ``` [http-parser](https://github.com/nodejs/http-parser)在解析客戶端請求也是基於事件來對資料進行處理: - `kOnHeaders`:不斷解析請求頭 - `kOnHeadersComplete`:請求頭解析完成 - `kOnBody`:不斷解析請求體 - `kOnMessageComplete`:請求體解析完成 TCP在進行資料傳輸的過程中,會將超出緩衝區剩餘空間大小的資料進行拆包,使得同一個請求資料包可能分多次傳送給服務端。這裡`kOnHeaders`和`kOnBody`就是用於拼接被拆分的資料,組合同一個請求的資料。 當請求頭解析完成以後,會執行`kOnHeadersComplete`回撥函式,在這個回撥函式中會生成`req`物件。 ```js // lib/_http_common.js const { IncomingMessage } = require('_http_incoming'); // 請求頭解析完成後執行的回撥函式 function parserOnHeadersComplete(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { const parser = this; const { socket } = parser; // ... // 絕大多數情況下socket.server[kIncomingMessage]等於IncomingMessage const ParserIncomingMessage = (socket && socket.server && socket.server[kIncomingMessage]) || IncomingMessage; const incoming = parser.incoming = new ParserIncomingMessage(socket); // ... return parser.onIncoming(incoming, shouldKeepAlive); } // lib/_http_incoming.js function IncomingMessage(socket) { // ... } ``` `kOnHeadersComplete`回撥中例項出來的`IncomingMessage`物件就是`req`物件。回撥最後會執行`parser.onIncoming`函式,生成`res`物件。 ```js // lib/_http_server.js function connectionListenerInternal(server, socket) { // ... // 這個就是kOnHeadersComplete回撥最後執行的函式 parser.onIncoming = FunctionPrototypeBind(parserOnIncoming, undefined, server, socket, state); // ... } // 第四個引數就是req物件,req物件是在parser.onIncoming(incoming, shouldKeepAlive)函式執行的時候傳入的incoming物件 function parserOnIncoming(server, socket, state, req, keepAlive) { // ... ArrayPrototypePush(state.incoming, req); // 例項res物件 const res = new server[kServerResponse](req); if (socket._httpMessage) { ArrayPrototypePush(state.outgoing, res); } // ... // 這個事件會在呼叫res.end的時候觸發 res.on('finish', FunctionPrototypeBind(resOnFinish, undefined, req, res, socket, state, server)); // ... server.emit('request', req, res); // 釋出request事件,執行createServer函式呼叫傳入的業務處理函式 // ... } // 這裡的ServerResponse繼承於OutgoingMessage類,後續將會介紹到 this[kServerResponse] = options.ServerResponse || ServerResponse; ``` 當`req`和`res`物件都初始成功並存放後,就會執行createServer函式呼叫傳入的業務處理函式。 當`req`生成後,邊會執行`parserOnIncoming`生成`res`物件,同時會在`res`物件中註冊`finish`事件,當業務程式碼執行`res.end`的時候,就會觸發這個事件。當`req`和`res`物件都準備好後,就會發布`request`事件,同時將`req`和`res`物件傳入。`request`事件的回撥函式就是業務程式碼呼叫`http.createServer`時傳入的回撥函式。 ## res.end執行 ```js const http = require('http'); http.createServer((req, res) => { res.end('hello word'); }).listen(8080); ``` 當業務處理完成後,業務程式碼中主動呼叫`res.end()`函式,響應客戶端請求,接下來我們看下。 ```js // lib/_http_server.js function ServerResponse(req) { FunctionPrototypeCall(OutgoingMessage, this); // ... } ObjectSetPrototypeOf(ServerResponse.prototype, OutgoingMessage.prototype); ObjectSetPrototypeOf(ServerResponse, OutgoingMessage); ``` `ServerResponse`類是從`OutgoingMessage`類繼承的。業務中使用的`res.end`方法也是在`OutgoingMessage`中進行定義的,下面我們看下`OutgoingMessage`類實現。 ```js // lib/_http_outgoing.js function OutgoingMessage() { // ... this._header = null; // ... } OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { //... if (chunk) { // ... write_(this, chunk, encoding, null, true); } // 訂閱finish事件,回撥函式是res.end呼叫時傳入的callback if (typeof callback === 'function') this.once('finish', callback); // ... // 使用write_將響應資料寫入響應請求的內容中,然後執行_send繫結finish函式,當資料響應完成後,就會觸發執行這個finish函式 const finish = FunctionPrototypeBind(onFinish, undefined, this); this._send('', 'latin1', finish); } function write_(msg, chunk, encoding, callback, fromEnd) { // ... len = Buffer.byteLength(chunk, encoding); // ... if (!msg._header) { if (fromEnd) { msg._contentLength = len; } } //... // 業務程式碼中呼叫res.end,_header為null,_implicitHeader函式在lib/_http_server.js中被重寫,_implicitHeader執行會將一個header+CRLF賦值給msg._header if (!msg._header) { msg._implicitHeader(); } // ... ret = msg._send(chunk, encoding, callback); // ... } OutgoingMessage.prototype._send = function _send(data, encoding, callback) { if (!this._headerSent) { if (typeof data === 'string' && (encoding === 'utf8' || encoding === 'latin1' || !encoding)) { // _implicitHeader函式生成為_header賦值響應頭+CRLF,因此這裡的data最終的值為響應頭+CRLF+響應體 data = this._header + data; } else { const header = this._header; ArrayPrototypeUnshift(this.outputData, { data: header, encoding: 'latin1', callback: null }); } this._headerSent = true; } return this._writeRaw(data, encoding, callback); }; OutgoingMessage.prototype._writeRaw = _writeRaw; function _writeRaw(data, encoding, callback) { const conn = this.socket; // ... if (conn && conn._httpMessage === this && conn.writable) { // ... // 將響應的內容新增到響應緩衝區,並寫出返回給使用者,當寫出成功以後執行回撥函式 return conn.write(data, encoding, callback); } // ... } ``` `res.end`在執行的時候,主要流程有兩個: - 呼叫`write_`函式,首先會生成響應頭,然後將響應頭存放到`_header`中,後續再生成響應內容,將響應內容(響應頭+CRLF+響應體)通過socket寫出響應給使用者。 - 呼叫`res._send`,向`socket.write`中寫入`finish`回撥函式,當服務端的響應內容完全寫出的時候執行`finish`函式,`finish`函式內部會發布`finish`事件。程式中有兩處監聽了`finish`事件: - `parserOnIncoming`函式中生成`res`物件後,會在上面監聽`finish`事件; - `res.end`函式中訂閱了一次`finish`事件,這裡的回撥函式主要是業務程式碼呼叫`res.end`時傳入的回撥函式。 ```js // 響應頭內容處理 // lib/_http_server.js ServerResponse.prototype._implicitHeader = function _implicitHeader() { this.writeHead(this.statusCode); }; ServerResponse.prototype.writeHead = writeHead; function writeHead(statusCode, reason, obj) { // ... this._storeHeader(statusLine, headers); // ... } // lib/_http_outgoing.js OutgoingMessage.prototype._storeHeader = _storeHeader; function _storeHeader(firstLine, headers) { // ... this._last = true; // ... this._header = header + CRLF; this._headerSent = false; // ... } ``` `_implicitHeader`執行會將響應頭+CRLF內容存放到`res._header`中,此時響應頭已經處理完,等到需要使用`socket.write`響應請求的時候,再取出來同響應體一同返回給客戶端。 ```js // lib/_http_server.js function parserOnIncoming(server, socket, state, req, keepAlive) { // 注意這裡也訂閱了res物件中的finish事件 res.on('finish', FunctionPrototypeBind(resOnFinish, undefined, req, res, socket, state, server)); } function resOnFinish(req, res, socket, state, server) { // 清除state中存放的req物件 ArrayPrototypeShift(state.incoming); clearRequestTimeout(req); clearIncoming(req); // 關閉res process.nextTick(emitCloseNT, res); // 關閉socket連線 if (res._last) { if (typeof socket.destroySoon === 'function') { socket.destroySoon(); } else { socket.end(); // socket斷開連線 } } } function emitCloseNT(self) { self.destroyed = true; self._closed = true; self.emit('close'); } ``` 當`finish`事件觸發,程式會首先將緩衝的`req`和`res`物件刪除,然後關閉`socket`連線,至此這個客戶端請求就處理完