欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解NODEJS的http实现

程序员文章站 2024-01-22 15:54:22
一、前言 目前,http协议是互联网上应用最为广泛的一种网络协议,也是前端er接触最多的一种协议。通过阅读http模块在nodejs中的实现,能够更深入的了解http协议...

一、前言

目前,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的网络请求,实际情况要更加复杂一些,还有一些细节没有完全搞清。等下次总结、分享,我会尽量把漏掉细节都补上。

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