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的方式实现客户端与服务端之间的长度连接,但是结束时务必关闭链接。