Open DavidCai1111 opened 8 years ago
收藏。
图片挂了可以修复一下
@fengmk2 发现啦,已修复~
req.timeout 和 res.timeout 分别会用在什么情况下呢?
2018 年底发来贺电,写的不错,同问上面那个问题
有个疑问,在触发connection事件,并在socket上添加上data事件,其回调函数中,有这么一句, var r = parser.execute(d),也就是说,当socket获取TCP推入的数据,触发data事件,然后会执行parser的周期函数。但当我debug的时候,我并没有看见调用socket的data事件,但他的确执行了parser的解析函数。
如果大家使用 Node.js 写过 web 应用,那么你一定使用过
http
模块。在 Node.js 中,起一个 HTTP server 十分简单,短短数行即可:就这么简单,因为 Node.js 把许多细节都已在源码中封装好了,主要代码在
lib/_http_*.js
这些文件中,现在就让我们照着上述代码,看看从一个 HTTP 请求的到来直到响应,Node.js 都为我们在源码层做了些什么。HTTP 请求的来到
在 Node.js 中,若要收到一个 HTTP 请求,首先需要创建一个
http.Server
类的实例,然后监听它的request
事件。由于 HTTP 协议属于应用层,在下层的传输层通常使用的是 TCP 协议,所以net.Server
类正是http.Server
类的父类。具体的 HTTP 相关的部分,是通过监听net.Server
类实例的connection
事件封装的:这时,则需要一个 HTTP parser 来解析通过 TCP 传输过来的数据:
值得一提的是,parser 是从一个“池”中获取的,这个“池”使用了一种叫做 free list(wiki)的数据结构,实现很简单,个人觉得是为了尽可能的对 parser 进行重用,并避免了不断调用构造函数的消耗,且设有数量上限(
http
模块中为1000
):由于数据是从 TCP 不断推入的,所以这里的 parser 也是基于事件的,很符合 Node.js 的核心思想。使用的是 http-parser 这个库:
所以一个完整的 HTTP 请求从接收到完全解析,会挨个经历 parser 上的如下事件监听器:
parserOnHeaders
:不断解析推入的请求头数据。parserOnHeadersComplete
:请求头解析完毕,构造 header 对象,为请求体创建http.IncomingMessage
实例。parserOnBody
:不断解析推入的请求体数据。parserOnExecute
:请求体解析完毕,检查解析是否报错,若报错,直接触发clientError
事件。若请求为 CONNECT 方法,或带有 Upgrade 头,则直接触发connect
或upgrade
事件。parserOnIncoming
:处理具体解析完毕的请求。所以接下来,我们的关注点自然是
parserOnIncoming
这个监听器,正是这里完成了最终request
事件的触发,关键步骤代码如下:可以看出,对于同一个 socket 发来的请求,源码中分别维护了两个队列,用于缓冲
IncomingMessage
实例和对应的ServerResponse
实例。先来的ServerResponse
实例先占用 socket ,监听其finish
事件,从各自队列中释放该ServerResponse
实例和对应的IncomingMessage
实例。比较绕,以一个简化的图示来总结这部分逻辑:
响应该 HTTP 请求
到了响应时,事情已经简单许多了,传入的
ServerResponse
已经获取到了 socket。http.ServerResponse
继承于一个内部类http.OutgoingMessage
,当我们调用ServerResponse#writeHead
时,Node.js 为我们拼凑好了头字符串,并缓存在ServerResponse
实例内部的_header
属性中:紧接着在调用
ServerResponse#end
时,将数据拼凑在头字符串后,添加对应的尾部,推入 TCP ,具体的写入操作在内部方法ServerResponse#_writeRaw
中:最后
到这,一个请求就已经通过 TCP ,发回给客户端了。其实本文中,只涉及到了一条主线进行解析,源码中还考虑了更多的情况,如超时,socket 被占用时的缓存,特殊头,上游突然出现问题,更高效的已写头的查询等等。非常值得一读。
参考: