第四章 请求的“附件”
email 的附件功能很好用, 附件可能是一个超级大的文件, 但不必等附件下载完毕,而可以先阅读文本部分。 甚至我们发现附件没什么用时,干脆不下载,节省时间、流量。
假设没有附件的这种机制:
email 在附件接收完毕前,
连是谁发来的都不知道,
那会怎样?
可能下载了半天,才能打开邮件,最后发现只是一个垃圾广告,甚至是有害信息。
在 http 通信中,也有这种机制。 http 通信中,数据分为两部分:header 和 body。 上上一章(收信、回信)中读取的 host、method、path 都在 header 里。 这一章介绍 “如何读取 http body”。
一般地,以下几种情况,会把数据放在 body,而不是 header 里:
- 发送 json 数据
- 发送 form 数据
- 文件上传
读 body
客户端发送的 body,可能很大, 因此后端在处理 body 时, 会在收到 “一小段” 数据后,就立刻交给我们处理。 看下面的代码你就明白了:
1const http = require('http')2
3const 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
14server.listen(8080)15console.log('ok')第 4-6 行,添加了一个监听器:每收到一小段数据时,就执行第 5 行。 数据量大时,会被分为很多段,以便尽早处理已经收到的数据,因此第 5 行可能会执行多次。
第 7-11 行,添加了另一个监听器:body 接收完毕时,执行 8-10 行。 因为 “完毕” 只有一次,所以第 8-10 行,只执行一次。
尽管上面的代码没有实际用途, 但是用最少的代码展示了 Node.js 读取 body 的机制。 下面读 json 的案例,尽管更实用一些,但不过只是技术细节,并不重要。
读 body 里的 json 数据
客户端往往这样发送 json:
1fetch('/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 封装程度较高,隐藏了一些细节,所以不好直接从代码观察。
在后端,只要把收到的所有 “一小段” 们拼接在一起就可以了:
1const http = require('http')2
3const 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
20server.listen(8080)21console.log('ok')而大多数 json 数据并不大,
所以 req.on('data', chunk => ...) 很多时候只执行一次。
封装 read_json 函数
可以把读取 json 的代码,写在一个函数里:
1const http = require('http')2
3const 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
11server.listen(8080)12console.log('ok')13
14function read_json(req) {15 return new Promise((resolve, reject) => {16 let body = ''17 req.on('data', chunk => {18 body += chunk19 })20 req.on('end', () => {21 const data = JSON.parse(body)22 resolve(data)23 })24 })25}另外还有两种异常:
- 数据传输过程中出现异常(比如传输中断)
- 数据解析时出现异常(非法 json)
于是可以给 read_json 加上异常处理代码:
1function read_json(req) {2 return new Promise((resolve, reject) => {3 let body = ''4 req.on('data', chunk => {5 body += chunk6 })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}