http server原始碼解析
阿新 • • 發佈:2021-02-24
本文主要過下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`連線,至此這個客戶端請求就處理完