在我们使用Node搭建服务的时候,一般都是直接用的express或koa,它们都是对http模块的再次封装,了解Node的http模块有利于加深对Node服务的理解。
服务端
比如我们可以搭建一个简单的http服务如下所示:
const http = require('http');
http.createServer((req, res) => {
console.log(req.method, req.url, req.httpVersion, req.headers);
res.setHeader('testHeader', "foo");
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.write('okok');
res.write('okok1');
res.write('okok2');
res.end('111');
}).listen('8181', () => {
console.log('8181端口启动成功');
});
curl查看连接过程
http模块也是对网络请求的再次封装,node服务接收到的实际是http报文,我们在控制台使用curl可以看到连接的整个过程。在shell输入curl -v http://127.0.0.1:8181/,可以得到如下的结果:
* Trying 127.0.0.1:8181...
* Connected to 127.0.0.1 (127.0.0.1) port 8181 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:8181
> User-Agent: curl/7.77.0
> Accept: */*
>
< HTTP/1.1 200 OK
< testHeader: foo
< Content-Type: text/plain
< Date: Sun, 29 May 2022 07:26:50 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Transfer-Encoding: chunked
<
okokokok1okok2111
* Connection #0 to host 127.0.0.1 left intact
其中前两行是TCP连接的过程,表示建立了连接。
* Trying 127.0.0.1:8181...
* Connected to 127.0.0.1 (127.0.0.1) port 8181 (#0)
>开头的是请求报文,第一行是请求行,标识请求的方法GET,请求的路径/,以及使用的协议HTTP/1.1。
后面的是请求头,再后面有一个空行,空行后面如果请求报文还有内容,则是请求体的内容。
> GET / HTTP/1.1
> Host: 127.0.0.1:8181
> User-Agent: curl/7.77.0
> Accept: */*
>
<开头的是响应报文,第一行是响应行,分别是协议、响应码和响应码对应的文本。再后面是响应头,空行后面是响应的内容。
< HTTP/1.1 200 OK
< testHeader: foo
< Content-Type: text/plain
< Date: Sun, 29 May 2022 07:26:50 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Transfer-Encoding: chunked
<
okokokok1okok2111
最后的是关闭连接的信息
* Connection #0 to host 127.0.0.1 left intact
http-parser
node在接收到报文后会使用http-parser模块对报文进行解析,将解析出的内容放到req对象里面。所以在上文中
console.log(req.method, req.url, req.httpVersion, req.headers);
// 会打印出
//GET / 1.1 { host: '127.0.0.1:8181', 'user-agent': 'curl/7.77.0', accept: '*/*' }
同时node也将响应的服务抽象成了res,我们可以通过res来设置响应的内容。
我们可以通过res.setHeader和res.writeHeader来设置返回响应头的内容,区别是res.setHeader一次只能设置一个,res.writeHeader可以一次设置多个。且当我们调用res.writeHeader后设置的报文头才会写入到连接中,所以如果我们不调用res.writeHeader,那么前面的res.setHeader都是无效的。
我们可以通过res.write和res.end来设置响应内容,两者都可以来设置响应内容,然后发送给客户端,区别是res.end除了调用res.write发送数据给客户端之后,还会发送一个end信号给客户端,表示这次响应结束。
客户端
我们也可以使用http模块来搭建我们的客户端系统:
const http = require('http');
const options = {
hostname: '127.0.0.1',
port: '8181',
path: '/',
method: 'GET',
}
const req = http.request(options, res => {
console.log('res status: ', res.statusCode);
console.log('res headers: ', res.headers);
const chunks = [];
res.on('data', chunk => {
chunks.push(chunk);
});
res.on('end', () => {
console.log(Buffer.concat(chunks).toString());
});
});
req.end();
我们可以使用http.request来建立http连接,http.request接收两个参数,第一个是options用来设置连接的一些配置,第二个是连接建立后的回调函数。
options主要用来配置服务器的域名或ip、端口号、路径、方法。建立连接后,我们可以在res对象上拿到连接的一些信息,比如:
res.statusCode: 200
res.headers: {
testheader: 'foo',
'content-type': 'text/plain',
date: 'Sun, 29 May 2022 07:11:29 GMT',
connection: 'close',
'transfer-encoding': 'chunked'
}
然后我们可以通过res.on监听data事件和end事件,data事件得到的内容是Buffer,我们在接收完所有数据后在end事件里通过Buffer.concat可以得到完整的Buffer数据,然后转成字符串使用。