给前端程序员的后端小教程

第四章 请求的“附件”

email 的附件功能很好用, 附件可能是一个超级大的文件, 但不必等附件下载完毕,而可以先阅读文本部分。 甚至我们发现附件没什么用时,干脆不下载,节省时间、流量。

假设没有附件的这种机制:
email 在附件接收完毕前, 连是谁发来的都不知道, 那会怎样?

可能下载了半天,才能打开邮件,最后发现只是一个垃圾广告,甚至是有害信息

在 http 通信中,也有这种机制。 http 通信中,数据分为两部分:header 和 body。 上上一章(收信、回信)中读取的 host、method、path 都在 header 里。 这一章介绍 “如何读取 http body”。

一般地,以下几种情况,会把数据放在 body,而不是 header 里:

读 body

客户端发送的 body,可能很大, 因此后端在处理 body 时, 会在收到 “一小段” 数据后,就立刻交给我们处理。 看下面的代码你就明白了:

读 http body
1
const http = require('http')
2
3
const server = http.createServer((req, res) => {
4
req.on('data', chunck => {
5
console.log('收到一小段数据:', chunck)
6
})
7
req.on('end', () => {
8
console.log('body 接收完毕')
9
res.write('大文件已收到!')
10
res.end()
11
})
12
})
13
14
server.listen(8080)
15
console.log('ok')

第 4-6 行,添加了一个监听器:收到一小段数据时,就执行第 5 行。 数据量大时,会被分为很多段,以便尽早处理已经收到的数据,因此第 5 行可能会执行多次。

第 7-11 行,添加了另一个监听器:body 接收完毕时,执行 8-10 行。 因为 “完毕” 只有一次,所以第 8-10 行,只执行一次。

尽管上面的代码没有实际用途, 但是用最少的代码展示了 Node.js 读取 body 的机制。 下面读 json 的案例,尽管更实用一些,但不过只是技术细节,并不重要。

读 body 里的 json 数据

客户端往往这样发送 json:

1
fetch('/user', {
2
method: 'POST',
3
body: JSON.stringify({
4
name: 'PPz',
5
year: 3,
6
}),
7
headers: {
8
'Content-Type': 'application/json'
9
},
10
})

这里又碰到了 Content-Type。实际上是非必需的,但有些库会读取 Content-Type,自动解析 body。

axios 发送 json 时,当然也不例外。但 axios 封装程度较高,隐藏了一些细节,所以不好直接从代码观察。

在后端,只要把收到的所有 “一小段” 们拼接在一起就可以了:

读 http body 里的 json
1
const http = require('http')
2
3
const server = http.createServer((req, res) => {
4
console.log('收到 http 请求')
5
6
let json_str = ''
7
req.on('data', chunck => {
8
console.log('收到一段 json')
9
json_str += chunck // 拼接
10
})
11
12
req.on('end', () => {
13
const data = JSON.parse(json_str) // 解析
14
console.log('json 接收完毕:', data)
15
res.write('json 已收到!')
16
res.end()
17
})
18
})
19
20
server.listen(8080)
21
console.log('ok')

而大多数 json 数据并不大, 所以 req.on('data', chunk => ...) 很多时候只执行一次。

封装 read_json 函数

可以把读取 json 的代码,写在一个函数里:

read_json
1
const http = require('http')
2
3
const server = http.createServer(async (req, res) => {
4
console.log('收到 http 请求')
5
const data = await read_json(req)
6
console.log('json 接收完毕:', data)
7
res.write('json 已收到!')
8
res.end()
9
})
10
11
server.listen(8080)
12
console.log('ok')
13
14
function read_json(req) {
15
return new Promise((resolve, reject) => {
16
let body = ''
17
req.on('data', chunk => {
18
body += chunk
19
})
20
req.on('end', () => {
21
const data = JSON.parse(body)
22
resolve(data)
23
})
24
})
25
}

另外还有两种异常:

于是可以给 read_json 加上异常处理代码:

捕获 json 异常
1
function read_json(req) {
2
return new Promise((resolve, reject) => {
3
let body = ''
4
req.on('data', chunk => {
5
body += chunk
6
})
7
req.on('error', err => {
8
console.error('接收 json 时发生异常')
9
reject(err) // 抛给上级
10
})
11
req.on('end', chunk => {
12
try {
13
const data = JSON.parse(body)
14
resolve(data)
15
} catch (err) {
16
console.error('解析 json 时发生异常')
17
reject(err)
18
}
19
})
20
})
21
}
上一章    路由与异常 下一章    简介数据库