前言
前两天node.js发布了新版本,想看看具体更新了啥,于是去官网找changelog看了看,顺便逛了逛其它栏目。没想到,在DOCS下的Guides发现了一篇好文,讲的是node.js对http请求的处理过程,虽然不是很适合初学者,但顺藤摸瓜,能挖掘出许多知识点,串联起来,干货满满。下面是译文,没有逐字逐句翻译,有添油加醋的地方,但不影响原文的表达。
译文
温馨提示
这篇文章目的在于阐释HTTP请求在node.js中的处理过程。所以前提是假定你知道HTTP为何物,并且对node.js的EventEmitters和Streams有所了解,否则,最好快速过一下有关的API。
创建服务器
任何一个node web server在代码某一处都会通过createServer创建一个web服务器对象.
作为参数传入createServer
的函数是http请求必由之路,因此也叫作请求处理函数。事实上,createServer
返回的server
对象是一个EventEmitter
,因此,上面那段代码也可以这么写:
|
|
当请求来临时,node.js会调用请求处理函数,并且封装好了两个常用对象:request和response。稍后我们会经常碰到这两个家伙的。
花开两朵,各表一枝。为了能够接收到http请求,还需要调用server
对象的listen
方法。多数情况下,你只需要传给listen
一个端口号。还有一些其他设置,感兴趣的话请参考这里
Method,URL 和 Headers
处理一个请求时,你想知道的第一件事可能就是看一下这个请求的method
和url
,然后才会有相应的处理。node.js把这两个信息放在了request
对象里了,直接调用即可:
注:request 对象是 IncommingMessage的一个实例
Headers
也在request
对象里:
需要注意的是,无论客户端发送的是什么,node.js把所有的头信息关键词都小写化了。变单一的同时也就减少了因分歧出错的可能性。还有,如果有重复的头信息,有些会重写,有些会使用,
合并成字符串。在一些场景可能会出现问题,没关系,request
中还有个rawHeaders,你值得拥有。
Request Body(请求体)
当请求方法是PUT
或者POST
时,请求体就成了重点关注对象。获取请求体,相对于获取上面那三个值,就需要多知道点了:request
对象实现了ReadableStream接口,所以能够被监听或者管道化。因此,我们可以通过监听data
和end
事件来获取流内数据。
data
过来的数据块都是Buffer。如果你清楚的知道传输过来的数据是字符串,那么最好将它们存放在一个数组里,在end
事件中,合并(concatenate)并字符串化(stringify)。
|
|
注:多数情况下,这样做有些啰嗦。幸运的是,npm上有许多能将这些逻辑隐藏的优秀模块,比如concat-stream和body。即便如此,还是希望能够好好理解一下这个细节,因为这属于基础。
有关错误(Errors)
既然request
是一个EventEmitter
,那么当有错误时,就可以触发error
事件。如果你没有监听这个事件,错误会被抛出,进而很可能导致node.js程序的崩溃。所以,最佳实践便是给request
增加error
事件,在事件回调函数里面做一下日志记录的同时,最好给客户端返回对应的错误码,这个在后面会提到。
有关错误的处理,还有其它方式,可以参考这里。记住,错误随时会发生,要对此有所警惕,对其有专门的处理总是好的。
小结一下
走到这里,我们已经创建了一个web服务器,获取到了请求的method
,url
和headers
,哦,还有请求体内容。现在我们将这些放在一起:
很显然,如果运行这个代码,服务器能接收到请求(request),但没发出响应(response)。也就是说,在浏览器里面发出请求,会超时。
目前为止,我们还未碰触response
对象,它是ServerResponse的一个实例,也是一个WritableStream,为了将数据传回客户端,其中包含了许多实用方法。好吧,依旧是花开两朵,各表一枝,我们先认识下http状态码,待会儿再谈response
对象。
HTTP状态码
response
默认状态码是200
。当然,有些情况下,你需要返回不同的状态码。response
中的statusCode
属性就是为此存在的:
设置响应头
response
中的setHeader
该出场了:
需要注意的是,响应头关键词对大小写不敏感,如果重复设置一个响应头,那么客户端取到的是你最后一个。
显式发送响应头
上面提到的statusCode
和setHeader
属于隐式头部:意思是在发送body数据前,依赖的是node.js来发送头部数据。
如果你愿意,也可以显式地将头部信息写到响应流里。writeHead
便是为此而生:
设置完头部,接下来便是发送响应数据了。
发送响应数据
既然response
对象是个WritableStream
,那么就可以使用流方法来向客户端写数据了。
以上代码也可以简写成以下形式:
注:响应体在响应头之后,因此往response里写数据之前就设置好状态码和头信息,一切才会有意义。
Response的错误处理
与request
一样,response
也会触发error
事件。所以,有关request
错误处理最佳实践,同样也适用于response
。
再来小结一下
目前来讲,我们已经不会让浏览器傻等了。那么,把所有代码放在一起,我们可以做到让服务端把浏览器过来的请求组织下数据再传送过去,注意,使用JSON.stringify
格式化了下数据:
Echo 服务器
基于上面代码,我们可以简化一下,做出一个Echo服务器,即请求什么数据,就返回什么数据。我们只需要从请求里面获取数据并写到响应里,和上面代码差不多:
好吧,有些过于简单,我们再增加两个需求,满足下面两个条件才给出正确响应:
1.请求的method
是GET
2.URL是/echo
否则给出404
。
注:检查URL,实质上就是一种路由
routing
形式。其它形式有简单如swtich
语句,复杂如Express框架。如果需要纯路由功能,可以试试[Router][https://www.npmjs.com/package/router]。
上面的代码能不能再精简下呢?别忘了,request
对象是一个ReadableStream
,response
对象是一个WritableStream
。这意味着可以使用管道(pipe)直接将数据从一端传到另一端。所以,更为精简的代码诞生了:
事情还没完,程序出错了怎么办?好吧,加上错误处理机制:在此,我们仅仅打印出错误,并将状态码置为404
。(更为详细的错误处理机制可以参考这里)
OK,node.js如何处理http请求,目前为止,我们已经把大部分的基础知识讲解到了。最后,我们总结下这些知识点:
- 实例化一个HTTP服务器,并设置一个请求处理函数,另外别忘了监听一个端口
- 从
request
获取headers
,url
,method
,body
等信息 - 根据
url
或者其它信息路由 - 通过
response
发送响应头、状态码和数据 request
数据管道化到response
- 对
request
和response
设置错误处理机制