简单了解 node http 模块

文章记录了对http 模块的简单使用与理解。

  • http 服务端
  • http 客户端
  • 总结

1. http 服务端

先写个小例子

服务端:

let http = require('http')
let server = http.createServer((req, res) => {
  let buf = []
  req.on('data', data => {
    buf.push(data)
  })
  req.on('end', () => {
    let str = Buffer.concat(buf).toString()
    res.end(str)
  })
})

server.listen(8080, () => {
  console.log('server start')
})
大概代码执行流程:
 
http.createServer() 返回新建的 http.Server 实例。http.Server 继承自net.Server。requestListener 是一个自动添加到到 'request' 事件的函数。
'request' 事件 每次请求的时候都会触发。http服务是以request为单位进行服务的
http.IncomingMessage 对象,它可用于访问本次,请求的请求方法、消息头、以及数据(图中将,请求头与数据拆分开)。
http.ServerResponse 对象,服务端通过http.ServerResponse 实例,来给客户端(数据请求方)返回本次数据。包括响应头,响应体(内部通过socket来发送信息)。
http服务端:在TCP模块套接字 socket 上,接受的数据解析出请求报文头和报文主体,发送的响应报文头和报文主体组装成数据发送出去

1.1 请求:

请求报文由以下组成

  • 报文首部:请求方法、请求 URI、协议版本、可选的请求首部字段等
  • 空行
  • 报文主体:内容实体构成数据。

node http 模块 在TCP连接的读操作上,将数据解析成(以空行分割,报文头和报文体):

报文头部分:
req.method
req.httpVersionMajor
req.httpVersionMinor
req.httpVersion
req.upgrade
...
req.headers = {
  Content-Length: 15
  Content-Type: application/x-www-form-urlencoded
... ...
}
//
报文主体部分:
一个可读流对象req,可以继续报文主体数据的读取

1.2响应

服务端通过http.ServerResponse 实例,来向客户端(数据请求方)返回本次请求数据。包括响应头,响应体(内部通过socket来发送信息)。

node http 模块 在TCP连接的写操作上,将数据写缓存拼接(以空行拼接,报文头和报文体)成:
//中间有个空行
"HTTP/1.1 200 OK
Date: Sat, 30 Nov 2019 05:10:05 GMT
Connection: close
Content-Length: 25

"
+ 请求体部分数据或全部数据

通过的方法

// 具体使用参考文档

// 设置头部,头部并没有发送 
response.setHeader(name, value)
// 设置头部,向请求发送响应头,此方法只能在消息上调用一次,并且必须在调用 response.end() 之前调用。
response.writeHead()
// 如果调用此方法并且尚未调用 response.writeHead(),则将切换到隐式响应头模式并刷新隐式响应头。
// 这会发送一块响应主体。 可以多次调用该方法以提供连续的响应主体片段。
response.write()
// 此方法向服务器发出信号,表明已发送所有响应头和主体,该服务器应该视为此消息已完成。 必须在每个响应上调用此 response.end() 方法。
response.end() 

2. http 客户端

客户端向 HTTP服务器发起请求:
let http = require('http')
let options = {
  host: 'localhost',
  port: 8080,
  method: 'POST',
  headers: {
    'content-type': 'application/x-www-form-urlencoded'
  }
}
// 请求并没有 发出 req 是个可写流
let req = http.request(options)

req.on('response', res => {
  console.log(res.headers)
  let buf = []
  res.on('data', data => {
    buf.push(data)
  })
  res.on('end', () => {
    console.log(Buffer.concat(buf).toString())
  })
})
// write 向请求体写数据
req.write('name=luoxiaobu&title=http')
// 实际的请求头将会与第一个数据块一起发送,或者当调用 request.end() 时发送。
req.end()

大概代码执行流程:

http.request() 返回 http.ClientRequest 类的实例。http.ClientRequest 内部创建了一个socket来发起请求。

ClientRequest 实例是可以看做可写流。如果需要使用 POST 请求上传文件,则写入到 ClientRequest 对象。

response事件 每次服务器端有数据返回响应时都会触发。

http.IncomingMessage 对象,它可用于访问本次服务器端返回的,响应状态、消息头、以及数据。

http客户端:在TCP模块套接字 socket 上,请求报文头和报文主体组装成数据发送出去,接受的数据解析出响应报文头和报文主体,

2.1 请求:

node http 模块 在TCP连接的写操作上,将数据写缓存拼接(以空行拼接,报文头和报文体)成:
//中间有个空行
"POST / HTTP/1.1
content-type: application/x-www-form-urlencoded
Host: localhost:8080
Connection: close
Transfer-Encoding: chunked

"
+ 请求体部分数据或全部数据

通过的方法

// 具体使用参考文档
const postData = querystring.stringify({
  'msg': '你好世界'
});

const options = {
  hostname: 'localhost:8080',
  path: '/upload',
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': Buffer.byteLength(postData)
  }
};
// 内部会处理 options 解析出头部
const req = http.request(options, (res) => {

});

// 这会发送一块响应主体。 可以多次调用该方法以提供连续的响应主体片段。
response.write()
// 此方法向服务器发出信号,表明已发送所有响应头和主体,该服务器应该视为此消息已完成。 必须在每个响应上调用此 response.end() 方法。
response.end() 

2.2 响应

响应报文图:

响应报文由以下组成

  • 报文首部:协议版本、状态码(表示请求成功或失败的数字代码)、用以解释状态码的原因短语、可选的响应首部字段
  • 空行
  • 报文主体:实体主体构成。

node http 模块 在TCP连接的读操作上,将数据解析成:

报文头部分:
res.statusCode = statusCode;
res.statusMessage = statusMessage;
res.httpVersionMajor
res.httpVersionMinor
res.httpVersion
res.upgrade
...
res.headers = {
  Content-Length: 15
  Content-Type: application/x-www-form-urlencoded
... ...
}
//
报文主体部分:
一个可读流对象,可以继续报文主体数据的读取

3. 总结

文章记录了对http 模块的简单使用与理解,要深入理解http 模块,还需多看文档,代码实践。

文中例子比较粗糙,理解不准确之处,还请教正。

参考资料:<<图解HTTP>>

https://nodejs.org/dist/latest-v13.x/docs/api/http.html

12-26 01:42