写点什么

http server 源码解析

  • 2022 年 4 月 24 日
  • 本文字数:3755 字

    阅读完需:约 12 分钟

}


// 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 用来解析客户端请求,当客户端数据解析成功后,就会生成一个?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 在进行数据传输的过程中,会将超出缓冲区剩余空间大小的数据进行拆包,使得同一个请求数据包可能分多次发送给服务端。这里?kOnHeaders?和?kOnBody?就是用于拼接被拆分的数据,组合同一个请求的数据。


当请求头解析完成以后,会执行?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.o 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 nIncoming?函数,生成?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 serverkServerResponse;


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 执行


=========


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;


// ...


}

用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
http server源码解析_Java_爱好编程进阶_InfoQ写作社区