http server源码解析
本文主要过下http生成服务和处理请求的主要流程,其他功能并未涉及。
使用例子
const http = require('http');
http.createServer((req, res) => {
res.end('hello word');
}).listen(8080);
例子中从生成服务,到接收请求,最后响应请求,其中主要的工作有4部分,分别是:
- 调用
http.createServer
来生成一个服务 - 调用
listen
函数监听端口 - 接收请求,生成
req
和res
对象 - 执行业务函数,执行
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
对象的过程中,会分别监听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.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;
当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;
// ...
}
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
事件触发,程序会首先将缓冲的req
和res
对象删除,然后关闭socket
连接,至此这个客户端请求就处理完成了。
http server源码解析的更多相关文章
- Spring-cloud & Netflix 源码解析:Eureka 服务注册发现接口 ****
http://www.idouba.net/spring-cloud-source-eureka-client-api/?utm_source=tuicool&utm_medium=refer ...
- Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- kubernetes源码解析---- apiserver路由构建解析(1)
kubernetes源码解析---- apiserver路由构建解析(1) apiserver作为k8s集群的唯一入口,内部主要实现了两个功能,一个是请求的路由和处理,简单说就是监听一个端口,把接收到 ...
- QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数
QT源码解析(一) QT创建窗口程序.消息循环和WinMain函数 分类: QT2009-10-28 13:33 17695人阅读 评论(13) 收藏 举报 qtapplicationwindowse ...
- Koa2 源码解析(1)
Koa2 源码解析 其实本来不想写这个系列文章的,因为Koa本身很精简,一共就4个文件,千十来行代码. 但是因为想写 egg[1] 的源码解析,而egg是基于Koa2的,所以就先写个Koa2的吧,用作 ...
- OKHttp源码解析
http://frodoking.github.io/2015/03/12/android-okhttp/ Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 A ...
- Appium Server源码分析之作为Bootstrap客户端
Appium Server拥有两个主要的功能: 它是个http服务器,它专门接收从客户端通过基于http的REST协议发送过来的命令 他是bootstrap客户端:它接收到客户端的命令后,需要想办法把 ...
- FileZilla客户端源码解析
FileZilla客户端源码解析 FTP是TCP/IP协议组的协议,有指令通路和数据通路两条通道.一般来说,FTP标准命令TCP端口号是21,Port方式数据传输端口是20. FileZilla作为p ...
随机推荐
- python yield初探 (转)
1. iterator叠代器最简单例子应该是数组下标了,且看下面的c++代码: int array[10]; for ( int i = 0; i < 10; i++ ) printf(& ...
- 【有趣的全彩LED | 编程】用STM32 HAL库让WS2812B为你所动
一.效果展示 观看演示效果:https://www.bilibili.com/video/BV1dv411Y7x3 使用STM32 HAL库编程 PWM+DMA控制输出,CubeMX生成初始工程 实现 ...
- SpringBoot整合spring-security-oauth2完整实现例子
SpringBoot整合spring-security-oauth2完整实现例子 技术栈 : springboot + spring-security + spring-oauth2 + mybati ...
- Jenkins(4)docker容器内部修改jenkins容器时间
前言 用docker搭建的Jenkins环境时间显示和我们本地时间相差8个小时,需修改容器内部的系统时间 查看时间 查看系统时间 date-R 进入docker容器内部,查看容器时间 docker e ...
- 调试lcd时候给linux单板移植tslib
作者:良知犹存 转载授权以及围观:欢迎添加微信公众号:Conscience_Remains 总述 tslib背景: 在采用触摸屏的移动终端中,触摸屏性能的调试是个重要问题之一,因为电磁噪声的缘故,触 ...
- [CF套题] CF-1163
CF-1163 传送门 # Penalty A B1 B2 C1 C2 D E F 3 (483) 464 +0 0:06 +1 01:13 +3 01:12 + 01:57 + 01:56 A 第一 ...
- Codeforces Round #648 (Div. 2) B. Trouble Sort
一开始读错题了...想当然地认为只能相邻元素交换...(然后换了两种写法WA了4发,5分钟切A的优势荡然无存) 题目链接:https://codeforces.com/contest/1365/pro ...
- Codeforces Round #582 (Div. 3) C. Book Reading
传送门 题意: 给你n,k.表示在[1,n]这个区间内,在这个区间内找出来所有x满足x%k==0,然后让所有x的个位加到一起(即x%10),输出. 例如:输入10 2 那么满足要求的数是2 4 6 8 ...
- 2019 ICPC Asia Taipei-Hsinchu Regional Problem K Length of Bundle Rope (贪心,优先队列)
题意:有\(n\)堆物品,每次可以将两堆捆成一堆,新堆长度等于两个之和,每次消耗两个堆长度之和的长度,求最小消耗使所有物品捆成一堆. 题解:贪心的话,每次选两个长度最小的来捆,这样的消耗一定是最小的, ...
- python中is,== 和 in 的区别
python对象的三个基本要素:id(身份标识),type(数据类型)和value(值). is 运算符:判断的是对象间的唯一身份标识(id). == 运算符:判断的是对象间的value(值)是否相同 ...