Node
的HTTP
模块包含对http
处理的封装。在node
中,HTTP
服务继承自TCP
服务器(net
模块),它能够与多个客户端保持连接,由于其采用时间驱动的形式,并不为每一个连接创建额外的线程或进程,保持很低的内存占用,所以能实现高并发。
HTTP服务于tcp服务模型的比较
区别
- 在开启
Keepalive
后,一个TCP
会话可以用于多次请求和响应。- 理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连 接之前,TCP 连接都将被一直保持下去。
HTTP
在每次请求结束后都会主动释放连接,因此HTTP
连接是一种“短连接”.TCP
服务以connection
为单位进行服务,HTTP
服务以request
为单位进行服务。HTTP
模块即是将connection
到request的过程进行的封装。
联系http
是要基于TCP
连接基础上的,TCP
就是单纯建立连接,不涉及任何我们需要请求的实际数据,简单的传输。
http
是用来收发数据,即实际应用上来的。- 客户端和应用服务器建立TCP连接之后,就需要用
http
协议来传送数据了,HTTP
协议简单来说,还是请求,确认,连接。
HTTP流程
HTTP
模块将连接所用套接字的读写抽象为ServerRequest
和ServerResponse
对象,它们对应请求和响应操作。在请求产生的过程中,HTTP
模块拿到连接中传来的数据,调用二进制模块http_parser
进行解析,在解析完请求报文得我爆头后,触发request
事件,调用用户的业务逻辑。
HTTP流程的示意图
上面的处理程序对应到的代码就是下面响应Hello World这部分,代码如下:
function(req, res){
res.wirteHead(200,{'content-type': 'textt/plain'});
res.end(' Hello World ')
}
HTTP请求
对于TCP
连接的读操作,http
模块将其封装为serverRequest
对象。
通过curl
获取http
服务的报文,报文的头部会通过http_parser进行解析。报文如下:
> GET / HTTP/1.1
> Host: 127.0.0.1:1337
> User-Agent: curl/7.55.1
> Accept: */*
报文头第一行 GET / HTTP/1.1
被解析之后分解为如下属性。
req.method
属性:值为GET
,是为请求方法,常见的请求方法有GET、POST、DELETE、PUT、CONNECT
等几种。req.url
属性:值为/
。req.httpVersion
属性:值为1.1
。
其余报文都是很规律的key:value
格式,被解析后放置在req.headers属性上传递给业务逻辑以供调用,如下:
headers:{
User-Agent: curl/7.55.1,
Host: 127.0.0.1:1337,
Accept: */*
}
报文体部分则抽象为一个制度对象,如果业务逻辑需要读取报文体中的数据,则要在这个数据流结束后才能进行操作,如下:
function (req, res) {
console.log(req.headers);
var buffers = [];
req.on('data', function (trunk) {
buffers.push(trunk);
}).on('end', function () {
var buffer = Buffer.concat(buffers);
//TODO
res.end('hello world')
});
}
HTTP
请求对象和HTTP
响应对象是相对较底层的封装,web框架如Connect
和Express
都是在这两个对象基础上进行搞成封装完成的。
HTTP响应
HTTP
响应对象相对简单一些,它封装了对底层连接的写操作,可以将其看成一个可写的流对象。它影响响应报文头部信息的API为res.setHeader()和res.weiteHead():
res.writeHead(200, {'Content-Type':'text/plain'});
其分为setHeader()
和writeHead()
两个步骤。它在http模块封装下,实际生成如下报文:
< HTTP/1.1 200 OK
< Content-Type: text/plain
- 可调用
setHeader
进行多次设置,但只有调用writeHead
后,报文才会写入到连接中。
HTTP模块会自动帮你设置一些头信息:
< Date: Wed, 11 Mar 2020 08:32:20 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
-
报文体部分则是调用
res.write()
和res.end()
方法实现。 -
res.end()
和res.write()
差别在于end()
会先调用write()
发送数据,然后发送信号告知服务器这次响应结束。 - 响应结束后,
HTTP
服务器可能会将当前的链接用于下一个请求,或者关闭链接。 - 报头是在报文体发送前发送,一旦开始了数据的发送,
writeHead
和setHeader
将不会在生效。 - 服务端在处理业务逻辑时无论是否发生异常,务必在结束时调用
res.end
的方式结束请求,以防客户端一直处于等待的状态。 - 可通过延迟
res.end
的方式实现客户端与服务端之间的长度连接,但是结束时务必关闭链接。