最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • http server源码解析

    正文概述 掘金(duan777)   2021-02-25   417

    本文主要过下http生成服务和处理请求的主要流程,其他功能并未涉及。

    使用例子

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

    例子中从生成服务,到接收请求,最后响应请求,其中主要的工作有4部分,分别是:

    • 调用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中的listen

    在实例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对象生成过程。

    // 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后,我们继续来看下客户端请求的解析。

    // 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在解析客户端请求也是基于事件来对数据进行处理:

    • kOnHeaders:不断解析请求头
    • kOnHeadersComplete:请求头解析完成
    • kOnBody:不断解析请求体
    • kOnMessageComplete:请求体解析完成

    TCP在进行数据传输的过程中,会将超出缓冲区剩余空间大小的数据进行拆包,使得同一个请求数据包可能分多次发送给服务端。这里kOnHeaderskOnBody就是用于拼接被拆分的数据,组合同一个请求的数据。

    当请求头解析完成以后,会执行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连接,至此这个客户端请求就处理完成了。


    起源地下载网 » http server源码解析

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元