Node 源碼 —— http 模塊

http 模塊

http 模塊位於 /lib/http.js,我們直接看該模塊的核心方法 createServer

function createServer(opts, requestListener) {
  return new Server(opts, requestListener);
}

createServer 的作用是創建了一個 Server 類,該文件位於 /lib/_http_server.js

function Server(options, requestListener) {
  if (!(this instanceof Server)) return new Server(options, requestListener);

  if (typeof options === 'function') {
    requestListener = options;
    options = {};
  } else if (options == null || typeof options === 'object') {
    options = { ...options };
  } else {
    throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
  }

  // options 允許傳兩個參數,分別是 IncomingMessage 和 ServerResponse
  // IncomingMessage 是客戶端請求的信息,ServerResponse 是服務器響應的數據
  // IncomingMessage 和 ServerResponse 允許繼承後進行一些自定義的操作
  // IncomingMessage 主要負責將 HTTP 報文序列化,將報文頭報文內容取出序列化
  // ServerResponse 提供了一些快速響應客戶端的接口,設置響應報文頭報文內容的接口
  this[kIncomingMessage] = options.IncomingMessage || IncomingMessage;
  this[kServerResponse] = options.ServerResponse || ServerResponse;

  // Server 類仍然是繼承於 net.Server 類
  net.Server.call(this, { allowHalfOpen: true });

  if (requestListener) {
    // 觸發 request 請求事件後,調用 requestListener 函數
    this.on('request', requestListener);
  }

  // Similar option to this. Too lazy to write my own docs.
  // http://www.squid-cache.org/Doc/config/half_closed_clients/
  // http://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F
  this.httpAllowHalfOpen = false;

  this.on('connection', connectionListener);

  // 超時時間,默認禁用
  this.timeout = 0;
  // 對連接超出一定時間不活躍時,斷開 TCP 連接,重新建立連接要重新建立三次握手
  this.keepAliveTimeout = 5000;
  // 最大響應頭數,默認不限制
  this.maxHeadersCount = null;
  this.headersTimeout = 40 * 1000; // 40 seconds
}

我們需要進一步向下剖析,查看一下 net.Server 類是怎麼樣的,該文件位於 /lib/net.js

function Server(options, connectionListener) {
  if (!(this instanceof Server))
    return new Server(options, connectionListener);

  // 繼承於 EventEmitter 類,這個類屬於事件類
  EventEmitter.call(this);

  if (typeof options === 'function') {
    connectionListener = options;
    options = {};
    this.on('connection', connectionListener);
  } else if (options == null || typeof options === 'object') {
    options = { ...options };

    if (typeof connectionListener === 'function') {
      this.on('connection', connectionListener);
    }
  } else {
    throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
  }

  this._connections = 0;

  Object.defineProperty(this, 'connections', {
    get: deprecate(() => {

      if (this._usingWorkers) {
        return null;
      }
      return this._connections;
    }, 'Server.connections property is deprecated. ' +
       'Use Server.getConnections method instead.', 'DEP0020'),
    set: deprecate((val) => (this._connections = val),
                   'Server.connections property is deprecated.',
                   'DEP0020'),
    configurable: true, enumerable: false
  });

  this[async_id_symbol] = -1;
  this._handle = null;
  this._usingWorkers = false;
  this._workers = [];
  this._unref = false;

  this.allowHalfOpen = options.allowHalfOpen || false;
  this.pauseOnConnect = !!options.pauseOnConnect;
}
Object.setPrototypeOf(Server.prototype, EventEmitter.prototype);
Object.setPrototypeOf(Server, EventEmitter);

從代碼可以看出,net.Server 類本身是一個事件分發中心,具體的實現是由其多個內部方法所組成,我們先看一個我們最常用的方法 listen

Server.prototype.listen = function(...args) {
  const normalized = normalizeArgs(args);
  // 序列化以後 options 裏面包含了 port 和 host 字段(如果填寫了的話)
  var options = normalized[0];
  const cb = normalized[1];

  if (this._handle) {
    throw new ERR_SERVER_ALREADY_LISTEN();
  }

  if (cb !== null) {
    // 給回調函數綁定了一次性事件,當 listening 事件觸發時調用回調函數
    this.once('listening', cb);
  }
  
  // ... 

  var backlog;
  if (typeof options.port === 'number' || typeof options.port === 'string') {
    if (!isLegalPort(options.port)) {
      throw new ERR_SOCKET_BAD_PORT(options.port);
    }
    backlog = options.backlog || backlogFromArgs;
    // start TCP server listening on host:port
    if (options.host) {
      // 我們假設 Host 爲 localhost, port 爲 3000,那麼調用的就是 lookupAndListen 方法
      lookupAndListen(this, options.port | 0, options.host, backlog,
                      options.exclusive, flags);
    } else { // Undefined host, listens on unspecified address
      // Default addressType 4 will be used to search for master server
      listenInCluster(this, null, options.port | 0, 4,
                      backlog, undefined, options.exclusive);
    }
    return this;
  }

  // ...
};

function lookupAndListen(self, port, address, backlog, exclusive, flags) {
  if (dns === undefined) dns = require('dns');
  // 期間使用 dns 模塊對 host 進行解析,目的是爲了得到一個 ip 地址
  dns.lookup(address, function doListen(err, ip, addressType) {
    if (err) {
      self.emit('error', err);
    } else {
      addressType = ip ? addressType : 4;
      // 拿到 ip 地址後執行了 listenInCluster
      listenInCluster(self, ip, port, addressType,
                      backlog, undefined, exclusive, flags);
    }
  });
}

function listenInCluster(server, address, port, addressType,
                         backlog, fd, exclusive, flags) {
  exclusive = !!exclusive;

  // cluster 是 Node 中的集羣模塊,可以創建共享服務器端口的子進程
  // 在 listen 中的作用是啓用一個進程監聽 http 請求
  if (cluster === undefined) cluster = require('cluster');

  if (cluster.isMaster || exclusive) {
    // Will create a new handle
    // _listen2 sets up the listened handle, it is still named like this
    // to avoid breaking code that wraps this method
    // 這個方法是最終執行的方法
    server._listen2(address, port, addressType, backlog, fd, flags);
    return;
  }

  const serverQuery = {
    address: address,
    port: port,
    addressType: addressType,
    fd: fd,
    flags,
  };

  // Get the master's server handle, and listen on it
  cluster._getServer(server, serverQuery, listenOnMasterHandle);

  function listenOnMasterHandle(err, handle) {
    err = checkBindError(err, port, handle);

    if (err) {
      var ex = exceptionWithHostPort(err, 'bind', address, port);
      return server.emit('error', ex);
    }

    // Reuse master's server handle
    server._handle = handle;
    // _listen2 sets up the listened handle, it is still named like this
    // to avoid breaking code that wraps this method
    server._listen2(address, port, addressType, backlog, fd, flags);
  }
}

// server._listen2 指向的是一個 setupListenHandle 方法,setupListenHandle 最終指向的是 createServerHandle 方法
// Returns handle if it can be created, or error code if it can't
function createServerHandle(address, port, addressType, fd, flags) {
  var err = 0;
  // Assign handle in listen, and clean up if bind or listen fails
  var handle;

  var isTCP = false;
  if (typeof fd === 'number' && fd >= 0) {
    try {
      handle = createHandle(fd, true);
    } catch (e) {
      // Not a fd we can listen on.  This will trigger an error.
      debug('listen invalid fd=%d:', fd, e.message);
      return UV_EINVAL;
    }

    err = handle.open(fd);
    if (err)
      return err;

    assert(!address && !port);
  } else if (port === -1 && addressType === -1) {
    handle = new Pipe(PipeConstants.SERVER);
    if (process.platform === 'win32') {
      var instances = parseInt(process.env.NODE_PENDING_PIPE_INSTANCES);
      if (!Number.isNaN(instances)) {
        handle.setPendingInstances(instances);
      }
    }
  } else {
    // 最終啓動了一個 TCP 服務,當 TCP 端口收到數據時將會推送給 HTTP 進程
    // 具體實現可能是 TCP 內部發出了 emit 進行事件通知,同時將字節流轉換爲 HTTP 協議所能接受的報文格式(協議規範)
    handle = new TCP(TCPConstants.SERVER);
    isTCP = true;
  }

  if (address || port || isTCP) {
    debug('bind to', address || 'any');
    if (!address) {
      // Try binding to ipv6 first
      err = handle.bind6(DEFAULT_IPV6_ADDR, port, flags);
      if (err) {
        handle.close();
        // Fallback to ipv4
        return createServerHandle(DEFAULT_IPV4_ADDR, port);
      }
    } else if (addressType === 6) {
      err = handle.bind6(address, port, flags);
    } else {
      // 這一步進行主機和端口的綁定
      err = handle.bind(address, port);
    }
  }
  
  return handle;
}

從上面我們可以看出,http.createServer 的調用過程如下:

  • http.createServer 實際上是返回了一個 Server 類,而這個類本質上是繼承於 EventEmitter,用於接收和分發事件以通知外部調用,此時並沒有啓動進程。
  • http.createServer().listen() 這一步在底層,調用了 Node 的內建模塊 TCP 啓動了一個 TCP 進程並被動打開等待連接。
  • TCP 端口接收到連接時,接收來自發送端的數據,然後將其接收到的字節流傳遞給應用層後通過 http_parser 模塊分解得到符合 http 協議規範的報文格式,以事件(request)通知的形式通知給 Server 類。
  • Server 類在接收到事件通知時,將接收到的數據使用 IncomingMessage 類進行解析,將HTTP 報文序列化,將報文頭報文內容取出序列化設置爲 Javascript 的對象格式,同時使用 ServerResponse 類提供了一些快速響應客戶端的接口,設置響應報文頭報文內容的接口,然後將這兩項作爲 listen 的回調參數中的 (req, res) => {} 兩個參數傳入,觸發該回調函數。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章