第二章 收信、回信
读短信
收到前端发来的请求时,后端首先要做的,就是读请求:
- 请求的方法(比如
GET、POST、PUT、DELETE……) - 请求的路径(比如
/user、/user?id=2、/book、/book/2……) - 其他参数(比如“用于身份验证的 token”、“使用的 http 协议版本号”……)
1const http = require('http')2
3const server = http.createServer((req, res) => {4 console.log('收到请求', new Date())5 const method = req.method // 请求的 method6 const url_str = req.url // 请求的 url,字符串类型,格式大致为 /user?name=PPz&year=37 console.log(method, url_str, req.headers) // headers 里有很多默认参数8
9 res.write('Hello, Node.js')10 res.end()11})12
13server.listen(8080)14console.log('ok')不用解释吧,js 写后端就是这么简单直接。
我想你已经注意到了,上面的
req和res对象分别代表 request(请求)和 response(响应)。 如果你想读取前端发来的数据,就去req里找;想返回数据给前端,就使用res。
读路径里的参数
前端向后端发消息,一种重要的载体,就是路径:把参数放路径里。
像 /user?name=PPz&year=3,这个路径里就包含了 name 和 year 参数。
在 Node.js,这样读:
1const http = require('http')2
3const server = http.createServer((req, res) => {4 console.log('收到请求', new Date())5 const method = req.method6 const url_str = req.url // 请求的 url,字符串类型,格式大致为 /user?name=PPz&year=37 console.log(method, url_str, req.headers)8
9 const url = new URL(url_str, 'http://' + req.headers.host)10 const search_params = url.searchParams11 console.log(search_params.get('name')) // 'PPz'12 console.log(search_params.get('year')) // '3' 参数值都是字符串13 console.log(search_params.get('unreal')) // null 不存在的参数值为 null14
15 res.write('Hello, Node.js')16 res.end()17})18
19server.listen(8080)20console.log('ok')注意点 一
在浏览器环境(前端环境)里,也有 URL 这个全局构造器。
不太熟悉的同学,要查一查、搜一搜了。
Node.js 里的 URL 和浏览器里的 URL 是一样的。
其中的 url.searchParams 是一个 URLSearchParams 对象,浏览器环境里当然也有。
注意点 二
下面两种代码,发出的请求是一样的(所以解析方法也一样):
1// 代码一2axios.get('/user', {3 params: {4 name: 'PPz',5 year: 3,6 }7})8
9// 代码二10axios.get('/user?name=PPz&year=3')注意点 三
很多时候,前端上传的数据,不仅量大,而且结构复杂。比如:
1axios.post('/user', {2 name: 'PPz',3 year: 3,4 friends: [5 { name: 'CCz', year: 2 },6 { name: 'YYz', year: 2 },7 ]8})这种情况下,数据通常会以“附件”的形式,发给后端。解析 “附件” 比较复杂,这会在下下一章讨论。
回短信
要注意:“回”短信,是回复。在 http 通信中,后端只能回复。 没有请求时,后端就在那等着,啥也不干。 就像前端里,添加点击事件后,只要用户不点按钮,监听器里的代码永远不会执行。
扩展阅读(可以跳过)
websocket可以实现后端“主动”发消息。 但前提是,前端要事先主动发起连接、建立连接,这之后,后端才能“主动”发消息。websocket不在此小教程讨论范围内。
之前的例子中,都回复一个字符串。当然也可以回复 json:
1const http = require('http')2
3const server = http.createServer((req, res) => {10 collapsed lines
4 console.log('收到请求', new Date())5 const method = req.method6 const url_str = req.url // 请求的 url,字符串类型,格式大致为 /user?name=PPz&year=37 console.log(method, url_str, req.headers)8
9 const url = new URL(url_str, 'http://' + req.headers.host)10 const search_params = url.searchParams11 console.log(search_params.get('name')) // 'PPz'12 console.log(search_params.get('year')) // '3' 参数值都是字符串13 console.log(search_params.get('unreal')) // null 不存在的参数值为 null14
15 // res.write('Hello, Node.js')16 res.setHeader('Content-Type', 'application/json')17 const user = {18 name: 'PPz',19 year: 3,20 adult: false,21 }22 res.write(JSON.stringify(user))23 res.end()24})25
26server.listen(8080)27console.log('ok')可以看到,第 22 行,依然是回复一个字符串。 只不过,是把一个对象,先转化成 json 格式的字符串,然后响应。 实际上,其他编程语言、后端框架,在响应 json 时,也是一样的,并不存在什么“高级的东西”。
而第 16 行,是告诉客户端(前端):响应数据的格式(即 “content(内容) type(类型)是 json”)。
这里看起来有点奇怪:一般情况下,后端的同事,都是口头或文档告诉前端 “响应的格式是什么”,为什么要写在代码里?
这是因为,像 aioxs 这样库,在收到后端的回信(响应)后,会自动读取 header 里的 Content-Type 字段,如果发现是 application/json,
就会帮我们解析好(用 JSON.parse)。
但如果前端直接用浏览器内置的 fetch api,因为 fetch 没有自动解析的功能,
所以 Content-Type 就没必要设置了。
可以用 Postman 试一下,设不设置
Content-Type有什么区别。(注意:改了代码后,记得重启项目)
小结
如果一口气看了三章,那么建议动手敲一敲代码、探索探索,然后休息休息。
应了解:
- 读请求里的 method、url(包括 path 和 路径参数)、headers
- Content-Type(非必要)
- 响应 json
练习查询文档
以下内容,可以跳过
就我个人而言,一直使用的学习方法是(分两步): 第一步:先了解概念、流程,大致了解学的东西是什么、有哪些操作。 相比于“大教程”,这只占用很少的时间。
但细节,在“小教程”里是学不到的。 但是的但是,在“大教程”里真能学到“细节”? 虽然大教程里,会介绍细节,但细节之所以称为细节,就是因为细节多、难记。 除非你经常使用,否则,仅在教程里看一眼、听一句,其实是过目就忘。
所以,我学习时,第二步,也就是“了解大概”之后, 会去尝试使用它,遇到问题就查文档,或直接用搜索引擎。
比如 req 和 res 对象的官方文档:
- req: http.IncomingMessage
- res: http.ServerResponse
文档里的内容非常非常多,我们只需要根据目录,找我们想知道的就可以了。