http server源碼解析

本文主要過下http生成服務和處理請求的主要流程,其餘功能並未涉及。node

使用例子

const http = require('http');

http.createServer((req, res) => {
  res.end('hello word');
}).listen(8080);

例子中從生成服務,到接收請求,最後響應請求,其中主要的工做有4部分,分別是:git

  • 調用http.createServer來生成一個服務
  • 調用listen函數監聽端口
  • 接收請求,生成reqres對象
  • 執行業務函數,執行res.end響應請求

http.createServer和listen

// 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中的listengithub

在實例Server對象的過程當中,會分別監聽requestconnection這兩個事件。數據結構

  • connection:這裏監聽的就是net中的connection事件,當客戶端發起請求,TCP三次握手鍊接成功時,服務端就會觸發connection事件。connection事件的回調函數connectionListenerInternal將在下一個板塊進行講解。
  • request:當reqres對象都初始成功之後,就會發布request事件,前面代碼中咱們能夠看到request事件的回調函數requestListener就是開發者調用http.createServer時傳入的回調函數,這個回調函數會接收reqres兩個對象。

生成req、res對象

當客戶端TCP請求與服務端鏈接成功後,服務端就會觸發connection事件,此時就會實例一個http-parser用來解析客戶端請求,當客戶端數據解析成功後,就會生成一個req對象,接下來咱們先來看下req對象生成過程。socket

// 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實例來做爲客戶端請求的解析器。值得注意的是,這裏使用了free list數據結構來分配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 > 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數據結構。使用該數據結構目的是減小對象新建銷燬所帶來的性能消耗,它會維護一個長度固定的隊列,隊列中的全部對象大小都相同。當須要使用對象的時候,會優先從隊列中獲取空閒的對象,若是隊列中已經沒有可用的對象,就會新建一個與隊列中存放的對象大小相同的對象,供程序使用。對象使用完後,不會直接銷燬,而是會將對象壓入隊列中,直到後面被推出使用。性能

瞭解free list後,咱們繼續來看下客戶端請求的解析。ui

// 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在解析客戶端請求也是基於事件來對數據進行處理:this

  • kOnHeaders:不斷解析請求頭
  • kOnHeadersComplete:請求頭解析完成
  • kOnBody:不斷解析請求體
  • kOnMessageComplete:請求體解析完成

TCP在進行數據傳輸的過程當中,會將超出緩衝區剩餘空間大小的數據進行拆包,使得同一個請求數據包可能分屢次發送給服務端。這裏kOnHeaderskOnBody就是用於拼接被拆分的數據,組合同一個請求的數據。url

當請求頭解析完成之後,會執行kOnHeadersComplete回調函數,在這個回調函數中會生成req對象。

// 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對象。

// 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;

reqres對象都初始成功並存放後,就會執行createServer函數調用傳入的業務處理函數。
req生成後,邊會執行parserOnIncoming生成res對象,同時會在res對象中註冊finish事件,當業務代碼執行res.end的時候,就會觸發這個事件。當reqres對象都準備好後,就會發布request事件,同時將reqres對象傳入。request事件的回調函數就是業務代碼調用http.createServer時傳入的回調函數。

res.end執行

const http = require('http');

http.createServer((req, res) => {
  res.end('hello word');
}).listen(8080);

當業務處理完成後,業務代碼中主動調用res.end()函數,響應客戶端請求,接下來咱們看下。

// lib/_http_server.js
function ServerResponse(req) {
  FunctionPrototypeCall(OutgoingMessage, this);
  // ...
}

ObjectSetPrototypeOf(ServerResponse.prototype, OutgoingMessage.prototype);
ObjectSetPrototypeOf(ServerResponse, OutgoingMessage);

ServerResponse類是從OutgoingMessage類繼承的。業務中使用的res.end方法也是在OutgoingMessage中進行定義的,下面咱們看下OutgoingMessage類實現。

// 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時傳入的回調函數。
// 響應頭內容處理
// 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響應請求的時候,再取出來同響應體一同返回給客戶端。

// 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事件觸發,程序會首先將緩衝的reqres對象刪除,而後關閉socket鏈接,至此這個客戶端請求就處理完成了。

相關文章
相關標籤/搜索