详解NODEJS的http实现


Posted in NodeJs onJanuary 04, 2018

一、前言

目前,HTTP协议是互联网上应用最为广泛的一种网络协议,也是前端er接触最多的一种协议。通过阅读http模块在nodejs中的实现,能够更深入的了解HTTP协议。HTTP协议是基于TCP协议之上的应用层协议,它的实现离不开TCP/IP协议族。而具体到代码实现,http模块依赖于net模块。

如下图所示:在nodejs中,http通过net模块传输数据,得到数据之后依靠HTTP_PARSER对数据进行解析。

二、源码

启动一个HTTP服务

nodejs中启动一个HTTP服务很简单,就是实例化一个Server对象,并且监听某个端口:

const Server = require('./libs/http').Server
const server = new Server( function(req, res) { 
 res.writeHead(200)
 res.end('hello world')
})
server.listen(9999)

SERVER类

Server类继承于net.Server,并监听'connection‘事件。

在Server类中,主要做了两件事: 1. 初始化NET模块并建立TCP网络监听 2. 监听自身的request事件

当客户端请求到来的时候,Server实例会首先监听到 'connection' 事件,建立起TCP连接并在connectionListener中暴露出socket对象。接下来,HTTP模块就通过socket对象与客户端进行数据交互。

当一个请求到来后,Server会触发自身的 request 事件,调用 requestListener 方法,即创建Server实例时传入的回调函数。

new Server( function(req, res) { 
 res.writeHead(200)
 res.end('hello world')
})

注: socket对象类似于TCP协议的一个实现,可以通过它与客户端进行数据交互 注: 在 connectionListener 函数中,还初始化了parser实例,并给它绑定了一个 onIncoming 函数 HTTP Parser
整个解析流程在 connectionListener 中进行,socket 通过 'data' 事件获取TCP推入的数据

当socket获取到数据之后,会先对数据进行解析,即:parser.excute(),解析工具是parser。值得说明的是,作者为了实现对 parser 的重用, parser是从一个'FreeList池'中获取的。

...
const parser = parsers.alloc() 
...
connectionListener(socket) { 
  socket.on('data', socketOnData)

  // TCP推入数据,parser进行解析
  function socketOnData(d) {
    ...
    const ret = parser.execute(d)
    ...
  }
}

1、TCP数据到达时, 先执行execute()

2、顺藤摸瓜,我们发现parser.excute 就是 Excute(node_http_parser.cc)。而Excute也只是一个外包而已,具体工作是http_parser_excute(http_parser.c)搞定的。

node_http_parser.cc 只是对 http_parser.c 的一层包装,http_parser.c依靠对外暴露的7个回调周期函数与 node_http_parser.cc 进行数据交互。

3、http_parser.c只有两类回调:HTTP_CB、HTTP_DATA_CB。通过重载的方式,在这两类函数中注册了8个周期函数,如下图:

4、虽然http_parser注册有8个回调函数,但 node_http_parser.cc 对外只暴露出四个周期函数:

parserOnHeaders

parserOnHeadersComplete

parserOnBody

parserOnMessageComplete

5、当 http_parser.c 解析到 on_headers_complete 时,执行HTTP_CB(on_headers_complete)回调函数,如图:

函数内会执行 kOnHeadersComplete 回调函数,即:parserOnHeadersComplete 函数(common.js)

6、此时请求头解析基本完成,接下来创建一个IncomingMessage的实例,然后把请求头数据包装到该实例上。
执行 onIncoming 回调函数,并把得到的IncomingMessage实例作为参数传递进去。

function parserOnHeadersComplete (versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { 
  ...
  parser.incoming = new IncomingMessage(parser.socket)
  parser.incoming.httpVersionMajor = versionMajor
  parser.incoming.httpVersionMinor = versionMinor
  parser.incoming.httpVersion = versionMajor + '.' + versionMinor
  parser.incoming.url = url
  ...
  skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive)

}

7、 在 parserOnIncoming 中,创建一个ServerResponse实例。

具备了req、res两个实例,接下来触发Server监听的 request 事件。

在 Server 实例化时的,requestListener是作为函数参数对 request 事件进行监听的。

8、回到Server创建时:

const server = new Server( function(req, res) { 
  var data = ''
  req.on('data', function(chunk){
    console.log('chunk: ' + chunk)
    data += chunk;
  })
  res.writeHead(200)
  res.end('hello world')
})

综上所述,http_parser 解析完 header 之后,就会触发 request 事件。

那body数据放到哪里呢,其实body数据会一直放到流里面,直到用户使用data事件接收数据。也就是说,触发request的时候,body并不会被解析。

三、流程梳理

完整的http请求是这样的: - 客户端发起HTTP请求,首先触发Server端的connection事件,建立TCP链接。

Server接收到connection事件后,建立TCP连接,并暴露出套接字,通过套接字监听'data'事件;初始化http-parser,为后续解析数据备用。

HTTP请求数据到达Server端,parser执行execute方法进行解析,请求头解析成功后,通过回调触发request事件。

至此,我们在Server回调函数中,就接收到了此次http请求的request

四、结语

由于nodejs不少底层库都是C++/C编写的,在阅读、调试的过程中非常不便。我自己在读源码的时候,也只是着重看的JS部分源码。比如,TCP的三次握手、四次挥手,就没深究它的实现细节啦。 以上分析没有涉及到http-body的解析,对于有body的网络请求,实际情况要更加复杂一些,还有一些细节没有完全搞清。等下次总结、分享,我会尽量把漏掉细节都补上。

以上就是本次为大家分享的全部内容,感谢你对三水点靠木的支持。

NodeJs 相关文章推荐
NodeJS框架Express的模板视图机制分析
Jul 19 NodeJs
iPhone手机上搭建nodejs服务器步骤方法
Jul 06 NodeJs
nodejs入门教程二:创建一个简单应用示例
Apr 24 NodeJs
深入浅析Nodejs的Http模块
Jun 20 NodeJs
Nodejs 和Session 原理及实战技巧小结
Aug 25 NodeJs
nodejs简单访问及操作mysql数据库的方法示例
Mar 15 NodeJs
nodejs express配置自签名https服务器的方法
May 22 NodeJs
nodejs基础之buffer缓冲区用法分析
Dec 26 NodeJs
详解nodejs 配置文件处理方案
Jan 02 NodeJs
Nodejs实现的操作MongoDB数据库功能完整示例
Feb 02 NodeJs
nodejs的安装使用与npm的介绍
Sep 11 NodeJs
NodeJS实现一个聊天室功能
Nov 25 NodeJs
Nodejs中crypto模块的安全知识讲解
Jan 03 #NodeJs
nodejs+mongodb+vue前后台配置ueditor的示例代码
Jan 02 #NodeJs
nodejs操作mongodb的填删改查模块的制作及引入实例
Jan 02 #NodeJs
nodejs实现OAuth2.0授权服务认证
Dec 27 #NodeJs
使用nodejs+express实现简单的文件上传功能
Dec 27 #NodeJs
nodejs超出最大的调用栈错误问题
Dec 27 #NodeJs
nodejs实现简单的gulp打包
Dec 21 #NodeJs
You might like
php stripslashes和addslashes的区别
2014/02/03 PHP
laravel5.0在linux下解决.htaccess无效和去除index.php的问题
2019/10/16 PHP
通过javascript的匿名函数来分析几段简单有趣的代码
2010/06/29 Javascript
js 赋值包含单引号双引号问题的解决方法
2014/02/26 Javascript
JavaScript 模块化编程(笔记)
2015/04/08 Javascript
jquery实现的树形目录实例
2015/06/26 Javascript
使用CoffeeScrip优美方式编写javascript代码
2015/10/28 Javascript
Bootstrap框架动态生成Web页面文章内目录的方法
2016/05/12 Javascript
JavaScript 弹出子窗体并返回结果到父窗体的实现代码
2016/05/28 Javascript
分享一个原生的JavaScript拖动方法
2016/09/25 Javascript
AngularJS指令中的绑定策略实例分析
2016/12/14 Javascript
bootstrap日历插件datetimepicker使用方法
2016/12/14 Javascript
AngularJS中update两次出现$promise属性无法识别的解决方法
2017/01/05 Javascript
jQuery模拟淘宝购物车功能
2017/02/27 Javascript
解决Vue2.x父组件与子组件之间的双向绑定问题
2018/03/06 Javascript
JS/HTML5游戏常用算法之追踪算法实例详解
2018/12/12 Javascript
Nodejs核心模块之net和http的使用详解
2019/04/02 NodeJs
JS学习笔记之闭包小案例分析
2019/05/29 Javascript
9个JavaScript日常开发小技巧
2020/10/06 Javascript
[38:23]完美世界DOTA2联赛循环赛 FTD vs PXG BO2第二场 11.01
2020/11/02 DOTA
使用实现XlsxWriter创建Excel文件并编辑
2018/05/04 Python
Python实现八皇后问题示例代码
2018/12/09 Python
python得到一个excel的全部sheet标签值方法
2018/12/10 Python
python 搭建简单的http server,可直接post文件的实例
2019/01/03 Python
使用Python实现文字转语音并生成wav文件的例子
2019/08/08 Python
python利用datetime模块计算程序运行时间问题
2020/02/20 Python
浅析CSS3 中的 transition,transform,translate之间区别和作用
2020/03/26 HTML / CSS
美国汽车交易网站:Edmunds
2016/08/17 全球购物
什么是GWT的Module
2013/01/20 面试题
护理毕业生自荐信范文
2013/12/22 职场文书
军训鉴定表自我鉴定
2014/02/13 职场文书
幼儿生日活动方案
2014/08/27 职场文书
政府领导干部个人对照检查材料思想汇报
2014/09/24 职场文书
学校教学管理制度
2015/08/06 职场文书
mysql查找连续出现n次以上的数字
2022/05/11 MySQL
vue3不同环境下实现配置代理
2022/05/25 Vue.js