New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
通过源码解析 Node.js 中一个 HTTP 请求到响应的历程 #29
Labels
Comments
收藏。 |
图片挂了可以修复一下 |
@fengmk2 发现啦,已修复~ |
req.timeout 和 res.timeout 分别会用在什么情况下呢? |
2018 年底发来贺电,写的不错,同问上面那个问题 |
有个疑问,在触发connection事件,并在socket上添加上data事件,其回调函数中,有这么一句, |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
如果大家使用 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 被占用时的缓存,特殊头,上游突然出现问题,更高效的已写头的查询等等。非常值得一读。
参考:
The text was updated successfully, but these errors were encountered: