Node.js源碼解析-HTTP請求響應過程

Node.js源碼解析-HTTP請求響應過程

歡迎來個人博客閱讀:《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

Base

首先,讓咱們理一理思路緩存

_______
            |       | <== res
request ==> |   ?   | 
            |_______| ==> req
               /\
               ||
        http.createServer()
  • 先調用 http.createServer() 生成一個 http.Server 對象 ( 黑盒 ) 來處理請求數據結構

  • 每次收到請求,都先解析生成 req ( http.IncomingMessage ) 和 res ( http.ServerResponse ),而後交由用戶函數處理app

  • 用戶函數調用 res.end() 來結束處理,響應請求curl

綜上,咱們的切入點有:異步

http.createServer

讓咱們先來看看 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 )

解析生成 req 和 res

既然,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.incomingstate.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,處理完成,能夠響應並結束請求

res.end

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 事件,結束響應

End

到這裏,咱們已經走完了一個請求從接收到響應的過程

除此以外,在 http 模塊中,還考慮了許多的實現細節,很是值得一看

參考:

相關文章
相關標籤/搜索