详解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文件操作模块FS(File System)常用函数简明总结
Jun 05 NodeJs
基于NodeJS的前后端分离的思考与实践(四)安全问题解决方案
Sep 26 NodeJs
nodejs爬虫抓取数据之编码问题
Jul 03 NodeJs
nodejs如何获取时间戳与时间差
Aug 03 NodeJs
基于Nodejs利用socket.io实现多人聊天室
Feb 22 NodeJs
nodejs利用ajax实现网页无刷新上传图片实例代码
Jun 06 NodeJs
详解IWinter 一个路由转控制器的 Nodejs 库
Nov 15 NodeJs
nodejs爬虫初试superagent和cheerio
Mar 05 NodeJs
使用koa-log4管理nodeJs日志笔记的使用方法
Nov 30 NodeJs
nodejs一个简单的文件服务器的创建方法
Sep 13 NodeJs
详解NodeJs项目 CentOs linux服务器线上部署
Sep 16 NodeJs
nodejs中使用worker_threads来创建新的线程的方法
Jan 22 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实现时间轴函数代码
2011/10/08 PHP
php中判断文件存在是用file_exists还是is_file的整理
2012/09/12 PHP
PHP和javascript常用正则表达式及用法实例
2014/07/01 PHP
Yii框架日志操作图文与实例详解
2019/09/09 PHP
javascript编程起步(第六课)
2007/01/10 Javascript
javascript+xml技术实现分页浏览
2008/07/27 Javascript
几个常用的JavaScript字符串处理函数 - split()、join()、substring()和indexOf()
2009/06/02 Javascript
Json2Template.js 基于jquery的插件 绑定JavaScript对象到Html模板中
2011/10/29 Javascript
完美解决AJAX跨域问题
2013/11/01 Javascript
javascript实现ecshop搜索框键盘上下键切换控制
2015/03/18 Javascript
详谈javascript异步编程
2016/02/21 Javascript
微信小程序 常见问题总结(4058,40013)及解决办法
2017/01/11 Javascript
实现两个文本框同时输入的实例
2017/09/25 Javascript
Vue实现boradcast和dispatch的示例
2020/11/13 Javascript
Python获取单个程序CPU使用情况趋势图
2015/03/10 Python
Python递归遍历列表及输出的实现方法
2015/05/19 Python
玩转python爬虫之cookie使用方法
2016/02/17 Python
修改python plot折线图的坐标轴刻度方法
2018/12/13 Python
Flask框架踩坑之ajax跨域请求实现
2019/02/22 Python
Python除法之传统除法、Floor除法及真除法实例详解
2019/05/23 Python
详解python中自定义超时异常的几种方法
2019/07/29 Python
Python+redis通过限流保护高并发系统
2020/04/15 Python
Python实现密钥密码(加解密)实例详解
2020/04/26 Python
python装饰器实现对异常代码出现进行自动监控的实现方法
2020/09/15 Python
详解通过focusout事件解决IOS键盘收起时界面不归位的问题
2019/07/18 HTML / CSS
使用postMessage让 iframe自适应高度的方法示例
2019/10/08 HTML / CSS
澳大利亚领先的美容护肤品零售商之一:SkincareStore
2018/01/22 全球购物
法国足球商店:Footcenter
2019/07/06 全球购物
总经理助理职责
2014/02/04 职场文书
违反交通法规检讨书
2014/09/10 职场文书
村党的群众路线教育实践活动总结材料
2014/10/31 职场文书
运动会100米加油稿
2015/07/21 职场文书
《莫泊桑拜师》教学反思
2016/02/22 职场文书
2017元旦、春节期间廉洁自律承诺书
2016/03/25 职场文书
Go语言空白表示符_的实例用法
2021/07/04 Golang
Java实现扫雷游戏详细代码讲解
2022/05/25 Java/Android