# NodeJS 处理 HTTP,req 和 res、定义路由,综合实践
TIP
从本节内容开始学习 NodeJS 如何处理 HTTP 请求,通过前面的学习我们知道前后端交互的关系 是通过 HTTP 请求来做交互的。即可打通前端 和 NodeJS(服务端)
- Request(req)
- Response(res)
- 定义路由
- querystring
- Response 返回数据
- 获取 Request Body
# 一、认识 Request 和 Response
TIP
- NodeJS 如何监听 HTTP 请求
- 如何拿到 Request 和 Response
- 如何使用 Request 和 Response
# 1、NodeJS 如何监听 HTTP 请求
TIP
监听:即 前端发来的 HTTP 请求后,后端得拿到(或 得到监听)
注:
前端访问服务端,发起一个 HTTP 请求 到 服务端 -> 服务端拿到 Request 请求(监听)-> 服务端处理任何事情 -> Response(状态码、Content-type、body)返回给前端,整个过程就完成了。
上图也是,前端通过 Ajax 访问服务端的过程
- 也可简称:req 和 res,依然读作 Request 和 Response
- 通过 Request 可获取:method、url、body
- 通过 Response 可设置:状态码、Content-type、body(返回给前端的)
# 2、NodeJS 启动 Web 服务
TIP
启动 Web Server:即 监听 HTTP 请求
- NodeJS 中使用
http
模块,启动服务 - 本机的 IP:
127.0.0.1
- 本机的域名:
localhost
可通过本机 IP 或 本机域名来访问 Web 服务
新建项目 icoding-node-http
使用 VSCode 打开,在 /src/index.js
中
// 引入 http 核心模块
const http = require("http"); // commonJS 语法,require 三个层级:1、系统自带模块;2、npm 安装;3、自定义
// 创建 Web 服务
const server = http.createServer(() => {
// 处理请求
console.log("已经收到 http 请求"); // 打印证明请求得到了,否则看不出效果来
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
使用 node 命令运行
node .\src\index.js
在浏览器中输入 http://localhost:3000
回车
发现浏览器选项卡上边一直在加载中 ... 没有任何反应,此时看控制台打印 “已经收到 http 请求”
整个过程
在浏览器中输入 http://localhost:3000
回车 -> 发送 http 请求 -> 浏览器没有任何响应 -> 服务端已经收到了请求,控制台打印了 “已经收到 http 请求”,但并没有返回任何内容,因此浏览器也没有返回任何结果(显示一直加载中 ...)
到目前为止,说明前端的请求已经被服务端监听了
# 3、安装 nodemon
初始化 package.json
文件
npm init -y
安装 nodemon,在代码修改后会自动重启服务
# -D 或 --save-dev 开发依赖
npm i nodemon -D
在 package.json
中添加 nodemon 运行脚本
{
"scripts": {
"dev": "nodemon ./src/index.js"
}
}
此时就不用再使用 node ./src/index.js
命令启动服务了,直接使用以下命令即可
npm run dev
# 4、req 和 res
TIP
- req 即 Request,res 即 Response,是 http 请求的关键
- 如何拿到 req 和 res
- 如何使用 req 和 res
# 4.1、HTTP 的 GET 和 POST 请求
以下为 HTTP 的 GET 请求过程
以下 HTTP 的 POST 请求过程
# 4.2、获取 Request 和 Response
在 /src/index.js
中
// 引入 http 核心模块
const http = require("http");
// 创建 Web 服务
// req,Request 请求
// res,Response 响应
const server = http.createServer((req, res) => {
// 处理请求
res.end("<h1>Received request 已收到请求</h1>"); // res 返回信息给前端
// 目前返回的是字符串,一般我们会返回 JSON
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
启动运行,在浏览器中访问服务
# 4.3、处理中文字符
TIP
如何响应里包中文会出现乱码,我们需要在响应头里添加编码格式来处理乱码
在 /src/index.js
中
// 引入 http 核心模块
const http = require("http");
// 创建 Web 服务
// req,Request 请求
// res,Response 响应
const server = http.createServer((req, res) => {
// 设置响应头,设置编码集防止乱码
res.writeHead(200, {
"content-type": "text/html;charset=utf-8",
});
// 处理请求
res.end("<h1>Received request 已收到请求</h1>"); // res 返回信息给前端
// 目前返回的是字符串,一般我们会返回 JSON
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
添加完响应头编码后,再次重启服务
# 4.4、使用 Request 获取当前的 URL
在 /src/index.js
中
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, {
"content-type": "text/html;charset=utf-8",
});
// Request 获取当前 URL
const url = req.url;
console.log("url is:", url);
res.end("<h1>Received request 已收到请求</h1>");
});
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
保存后,nodemon 自动重启服务,浏览器访问 http://localhost:3000
,控制台打印输出 url 信息
如果浏览器中输入 http://localhost:3000/index.html?a=123
控制台打印输出
# 5、总结
TIP
- NodeJS 启动服务,
127.0.0.1
和localhost
- 如何拿到 req 和 res
- 如何使用 req 和 res
先简单了解即可,后边还会深入
# 二、定义路由
TIP
- 拿到 Request 中 的 url 和 method
- 定义路由(模拟评论系统的获取评论列表、创建评论信息)
- 测试路由(postman)
# 1、路由包含什么
TIP
- 定义 method,如 GET / POST 等
- 定义 url 规则,如
/api/list
和/api/create
- 定义输入(Request body)和 输出(Response body)格式
# 1.1、前端 Ajax 请求时,需要 URL
TIP
来自 axios 官方文档示例 (opens new window),其中 .get
、.post
以及 URL 都需要按照服务端的规定来做(需要服务端先实现)
function getUserAccount() {
// url 规则
return axios.get("/user/12345");
}
function getUserPermissions() {
// url 规则
return axios.get("/user/12345/permissions");
}
axios.all([getUserAccount(), getUserPermissions()]).then(
axios.spread(function (acct, perms) {
// 两个请求现在都执行完成
})
);
# 1.2、路由是什么
TIP
- 路由:英文叫 router
- 服务端的入口规则
- 和前端的约定
路由就像一座城池的城门一样,不同的出入口门有不同的功能,需要按照约定进出
# 1.3、路由 和 URL
TIP
- GET 请求,
/api/list
路由 ->axios.get('/api/list?a=1')
- POST 请求,
'/api/create'
路由 ->axios.post('/api/create', { ... })
路由是规则,url 是具体的形式
# 2、NodeJS 定义路由
TIP
- 从 req 中获取 url 和 method
- 判断 method 是否符合
- 看 url 是否符合规则
获取评论信息列表案例中,请求的 URL 是 /api/list
、method 是 GET,到服务端后该如何判断该请求就是获取评论信息列表的呢 ?
需要判断请求的 URL 是否是
/api/list
、method 是否是 GET
同理提交评论案例,请求的 URL 是 /api/create
、method 是 POST,到服务端后该如何判断该请求就是提交评论的呢 ?
需要判断请求的 URL 是否是
/api/create
、method 是否是 POST
# 3、创建路由
在 icoding-node-http
项目的 /src/index.js
中
// 引入 http 核心模块
const http = require("http");
// 创建 Web 服务
const server = http.createServer((req, res) => {
// 设置响应头,设置编码集防止乱码
res.writeHead(200, {
"content-type": "text/html;charset=utf-8",
});
// 路由定义
// Request 获取当前 URL
const url = req.url;
// 获取 method
const method = req.method;
// 打印获取到的 URL 和 method
console.log(url);
console.log(method);
res.end("<h1>测试 url 和 method</h1>"); // 处理请求,返回给前端
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
启动服务后,在浏览器中输入 http://localhost:3000/api/list
,控制台中会打印输入对应的 URL /api/list
# 3.1、创建获取评论列表路由
在 /src/index.js
中
// 引入 http 核心模块
const http = require("http");
// 创建 Web 服务
const server = http.createServer((req, res) => {
// 设置响应头
res.writeHead(200, {
"content-type": "text/html;charset=utf-8",
});
// 路由定义
// Request 获取当前 URL
const url = req.url;
// 获取 method
const method = req.method;
// 打印获取到的 URL 和 method
console.log(url); // /api/list
console.log(method); // GET
// 定义路由:模拟获取评论列表
if (url === "/api/list" && method === "GET") {
// 如果命中路由,Response 返回给前端
res.end("<h1>这是模拟获取评论列表的路由 !</h1>");
} else {
// 未命中路由,Response 返回给前端 404
res.end("<h1>404</h1>");
}
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
在浏览器中输入 http://localhost:3000/api/list
正确的路由 和 错误的路由,观察返回给前端的信息
注:
如果浏览器地址栏输入 http://localhost:3000/api/list?a=123
时,在 /api/list
后边添加参数 ?a=123
此时,浏览器访问返回
404
,因为获取的 url 地址为/api/list?a=123
不等于/api/list
截取掉 ?
包括问号之后的所有参数,再作比较
在 /src/index.js
中
// 引入 http 核心模块
const http = require("http");
// 创建 Web 服务
const server = http.createServer((req, res) => {
// 设置响应头
res.writeHead(200, {
"content-type": "text/html;charset=utf-8",
});
// 路由定义
// Request 获取当前 URL
const url = req.url;
// 截取 ? 号前边的 url
const path = url.split("?")[0]; // /api/list
// 获取 method
const method = req.method;
// 打印获取到的 URL 和 method
// console.log(url) // /api/list?a=123
// console.log(method) // GET
// 定义路由:模拟获取评论列表
if (path === "/api/list" && method === "GET") {
// 如果命中路由,Response 返回给前端
res.end("<h1>这是模拟获取评论列表的路由 !</h1>");
} else {
// 未命中路由,Response 返回给前端 404
res.end("<h1>404</h1>");
}
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
此时,浏览器地址栏输入 http://localhost:3000/api/list?a=123
即可正常返回了
# 3.2、创建评论路由
在 /src/index.js
中
// 引入 http 核心模块
const http = require("http");
// 创建 Web 服务
const server = http.createServer((req, res) => {
// 设置响应头
res.writeHead(200, {
"content-type": "text/html;charset=utf-8",
});
// 路由定义
// Request 获取当前 URL
const url = req.url;
// 截取 ? 号前边的 url
const path = url.split("?")[0]; // /api/list
// 获取 method
const method = req.method;
// 打印获取到的 URL 和 method
console.log("url:" + url); // /api/list?a=123
// console.log(method) // GET
console.log("path:" + path);
// 定义路由:模拟获取评论列表
if (path === "/api/list" && method === "GET") {
// 如果命中路由,Response 返回给前端
res.end("<h1>这是模拟获取评论列表的路由 !</h1>");
// 定义路由:模拟创建评论
} else if (path === "/api/create" && method === "POST") {
res.end("<h1>这是创建评论的路由 !</h1>");
} else {
// 未命中路由,Response 返回给前端 404
res.end("<h1>404</h1>");
}
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
在浏览器地址栏中输入 http://localhost:3000/api/create
,服务端返回 404
原因是:
浏览器访问是 GET 请求,而 判断条件为 POST 请求。可使用 postman 模拟 POST 请求
# 3.3、路由测试
TIP
- GET 请求,直接用浏览器访问
- POST 请求,需要借助工具 - postman
- postman 安装 (opens new window)
在 postman 中使用 POST 请求访问创建评论路由,即可成功返回对应的内容
# 4、总结
TIP
- 再次复习理由的概念
- NodeJS 如何定义路由
- 安装 和 使用 postman
# 三、querystring
TIP
- 什么是 querystring
- NodeJS 获取 querystring
- 论结构化与非结构化
# 1、什么是 querystring
TIP
- querystring 查询字符串(也叫 URL 参数)
https://search.jd.com/Search?keyword=寸衫&enc=utf-8
- url 问号
?
后面的都是 querystring &
分割,key = value
形式,可继续扩展
# 2、querystring 的作用
TIP
- 动态网页的基石(web1.0 静态 -> web2.0 动态)
https://search.jd.com/Search?keyword=寸衫&lang=en
https://search.jd.com/Search?keyword=运动鞋&lang=zh-CN
以上
keyword
和lang
参数当传入不同的参数时,页面显示的结果就会不一样(即:动态)
# 3、如何利用 querystring 实现动态网页
TIP
- 服务端拿到 querystring
- 根据不同的 querystring,返回不同的内容
- 即:变化 querystring,变换内容(只要服务端支持)
# 3.1、解析 querystring
TIP
在浏览器中输入 http://localhost:3000/api/list?a=123&b=111
解析 ?
问号后面的参数
在 /src/index.js
中解析 querystring
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, {
"content-type": "text/html;charset=utf-8",
});
const url = req.url;
const path = url.split("?")[0]; // /api/list
// 获取 ? 后面的参数
const queryStr = url.split("?")[1]; // a=123
// console.log('queryStr', queryStr) // 当 url 为 /favicon.ico 时,queryStr 的值为 undefined
const method = req.method;
// console.log("url:" + url) // /api/list?a=123&b=111
// console.log("path:" + path)
// 解析 querystring
const query = {};
// /api/list?a=123&b=111 对问号后边的参数解析
// 由于浏览器访问时,获取 URL 会有 /favicon.ico 图标,需要判断,否则会报错
// 判断当 queryStr 为 undefined 时,判断是否为空,当然也可以使用 if 条件语句 或 以下方式
queryStr &&
queryStr.split("&").forEach((item) => {
// item 即:a=123 的形式
const key = item.split("=")[0]; // 'a'
const val = item.split("=")[1]; // '123'
query[key] = val; // { a: '123', b: '111' }
});
// 解析后的参数对象
console.log(query); // { a: '123', b: '111' }
// 定义路由:模拟获取评论列表
if (path === "/api/list" && method === "GET") {
res.end("<h1>这是模拟获取评论列表的路由 ! </h1>");
// 定义路由:模拟创建评论
} else if (path === "/api/create" && method === "POST") {
res.end("<h1>这是模拟创建评论的路由 ! </h1>");
} else {
// 未命中路由,Response 返回给前端 404
res.end("<h1>404</h1>");
}
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
启动 node 服务
node .\src\index.js
在浏览器中输入 http://localhost:3000/api/list?a=123&b=111
此时,成功获取解析后的参数对象
# 3.2、判断 querystring 参数
TIP
在浏览器地址栏中访问 http://localhost:3000/api/list?type=1
- 如:
type=1
表示全部评论 type=2
表示当前用户的评论信息
判断如果
type=1
做什么,type=2
做什么
在 /src/index.js
中
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, {
"content-type": "text/html;charset=utf-8",
});
const url = req.url;
const path = url.split("?")[0]; // /api/list
// 获取 ? 后面的参数
const queryStr = url.split("?")[1]; // a=123
// console.log('queryStr', queryStr) // 当 url 为 /favicon.ico 时,queryStr 的值为 undefined
const method = req.method;
// console.log("url:" + url) // /api/list?a=123&b=111
// console.log("path:" + path)
// 解析 querystring
const query = {};
// /api/list?a=123&b=111 对问号后边的参数解析
// 由于浏览器访问时,获取 URL 会有 /favicon.ico 图标,需要判断,否则会报错
// 判断当 queryStr 为 undefined 时,判断是否为空,当然也可以使用 if 条件语句 或 以下方式
queryStr &&
queryStr.split("&").forEach((item) => {
// item 即:a=123 的形式
const key = item.split("=")[0]; // 'a'
const val = item.split("=")[1]; // '123'
query[key] = val; // { a: '123', b: '111' }
});
// 解析后的参数对象
console.log(query); // { a: '123', b: '111' }
// 定义路由:模拟获取评论列表
if (path === "/api/list" && method === "GET") {
// ---------------------------------
// type=1 表示全部评论
if (query.type === "1") {
res.end("<h1>这是模拟获取评论列表的路由,【所有评论信息】");
}
// type=2 表示当前用户的评论信息
if (query.type === "2") {
res.end("<h1>这是模拟获取评论列表的路由,【当前用户的评论信息】");
}
// 定义路由:模拟创建评论
} else if (path === "/api/create" && method === "POST") {
res.end("<h1>这是模拟创建评论的路由 ! </h1>");
} else {
// 未命中路由,Response 返回给前端 404
res.end("<h1>404</h1>");
}
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
启动 node 服务
node .\src\index.js
在浏览器中输入 http://localhost:3000/api/list?type=1
或 修改参数 type=2
注:
以上就是 querystring 的作用,无论什么路由当前返回给前端的内容先暂时不管,后面再做讲解
同时,以上解析 querystring 的过程本质上也是 NodeJS 中
querystring
模块的源码实现
# 4、使用 querystring 模块
修改 /src/index.js
,使用 querystring
模块,解析 querystring 的部分
const http = require("http");
// 引入 querystring 模块
const querystring = require("querystring");
const server = http.createServer((req, res) => {
res.writeHead(200, {
"content-type": "text/html;charset=utf-8",
});
const url = req.url;
const path = url.split("?")[0]; // /api/list
// 获取 ? 后面的参数
const queryStr = url.split("?")[1]; // a=123
// console.log('queryStr', queryStr) // 当 url 为 /favicon.ico 时,queryStr 的值为 undefined
const method = req.method;
// console.log("url:" + url) // /api/list?a=123&b=111
// console.log("path:" + path)
// 解析 querystring
// const query = {}
// // /api/list?a=123&b=111 对问号后边的参数解析
// // 由于浏览器访问时,获取 URL 会有 /favicon.ico 图标,需要判断,否则会报错
// // 判断当 queryStr 为 undefined 时,判断是否为空,当然也可以使用 if 条件语句 或 以下方式
// queryStr && queryStr.split('&').forEach(item => {
// // item 即:a=123 的形式
// const key = item.split('=')[0] // 'a'
// const val = item.split('=')[1] // '123'
// query[key] = val // { a: '123', b: '111' }
// })
const query = querystring.parse(queryStr);
// 解析后的参数对象
console.log(query); // { type: '1' }
// 定义路由:模拟获取评论列表
if (path === "/api/list" && method === "GET") {
// ---------------------------------
// type=1 表示全部评论
if (query.type === "1") {
res.end("<h1>这是模拟获取评论列表的路由,【所有评论信息】");
}
// type=2 表示当前用户的评论信息
if (query.type === "2") {
res.end("<h1>这是模拟获取评论列表的路由,【当前用户的评论信息】");
}
// 定义路由:模拟创建评论
} else if (path === "/api/create" && method === "POST") {
res.end("<h1>这是模拟创建评论的路由 ! </h1>");
} else {
// 未命中路由,Response 返回给前端 404
res.end("<h1>404</h1>");
}
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
启动 node 服务
node .\src\index.js
在浏览器中输入 http://localhost:3000/api/list?type=1
或 修改参数 type=2
# 5、NodeJS 中 querystring 弃用的三种解决方法
TIP
在引入 NodeJS 的 querystring 模块后,发现会有删除线。即:官方 “已弃用”
# 5.1、升级 NodeJS 版本,修改引入方式
升级到 18.x
版本后修改引入方式即可
const querystring = require("node:querystring");
# 5.2、官方推荐 URLSearchParams 替代
const query = {};
// 使用 URLSearchParams 解析
for (const [name, value] of new URLSearchParams(queryStr)) {
query[name] = value;
}
console.log(query); // { type: '1' }
注:
另一种方式就是用我们上边最原始,手动解析 和 判断 querystring 参数的方式来实现,这也是使用插件 或 官方推荐的 URLSearchParams 方法的底层实现。
# 5.3、使用 querystringify 插件
TIP
由于 URLSearchParams 确实没有那么方便,也可以在项目里引入 querystringify 插件
首先安装插件
npm i querystringify
在 /src/index.js
中导入插件
// 导入插件,就和使用 querystring 一摸一样了
const querystring = require("querystringify");
// 解析参数
const query = querystring.parse(queryStr); // { type: '1' }
使用插件后的完整代码
const http = require("http");
// 引入 querystringify 模块
const querystring = require("querystringify");
const server = http.createServer((req, res) => {
res.writeHead(200, {
"content-type": "text/html;charset=utf-8",
});
const url = req.url;
const path = url.split("?")[0]; // /api/list
// 获取 ? 后面的参数
const queryStr = url.split("?")[1]; // a=123
// console.log('queryStr', queryStr) // 当 url 为 /favicon.ico 时,queryStr 的值为 undefined
const method = req.method;
// 解析参数
const query = querystring.parse(queryStr);
// 解析后的参数对象
console.log(query); // { type: '1' }
// 定义路由:模拟获取评论列表
if (path === "/api/list" && method === "GET") {
// ---------------------------------
// type=1 表示全部评论
if (query.type === "1") {
res.end("<h1>这是模拟获取评论列表的路由,【所有评论信息】");
}
// type=2 表示当前用户的评论信息
if (query.type === "2") {
res.end("<h1>这是模拟获取评论列表的路由,【当前用户的评论信息】");
}
// 定义路由:模拟创建评论
} else if (path === "/api/create" && method === "POST") {
res.end("<h1>这是模拟创建评论的路由 ! </h1>");
} else {
// 未命中路由,Response 返回给前端 404
res.end("<h1>404</h1>");
}
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
# 6、url 的 hash 能否有同样的作用
TIP
url 的 hash(哈希)路由,类似 Vue 中的哈希路由,即:#
后边的的部分
http://xxx.com/index.html/#/home
http://xxx.com/index.html/#/user/1
url 中
#
后边的部分就是 哈希
注:
hash 不能让服务端获取来实现动态网页,后端只能获取到 url 中 #
之前的部分,#
后边的部分是不能获取到的。
因此,hash 是不能传入到服务端。测试如下
# 7、结构化 与 非结构化
TIP
- 结构化的数据:易于通过程序访问和分析,如对象和数组
- 非结构化的数据:不易通过程序分析,如字符串
- 在编程中的数据,都尽量使用结构化的数据
如:上面学习的 querystring 就是非结构化的数据
a=123&b=111
- 对于字符串形式这样非结构化的数据,是不好确定其中是否有哪个参数的。如:确定 a 或 b
- 如果要将其变成结构化的形式需要通过原生解析 或
querystring
模块的形式 最终解析成 对象后,再来进行判断。
# 8、总结
TIP
- querystring 是
url?
后面的参数,格式key=val&key=val
- querystring 是动态网页的基石(hash 就不行)
- NodeJS 处理 querystring,并将其结构化
# 四、Response 返回数据
TIP
- 使用 res 设置返回的状态码,Content-type ,Body
- 如何返回 JSON 数据 ?
- 如何返回 HTML 数据 ?
# 1、前后端请求的过程
HTTP 的 GET 请求过程
注:
- 请求发送请求,通过浏览器 或 postman -> 服务端 ->
- 服务端通过 url 和 method 判断是否符合路由规则 ->
- 符合路由规则后 -> 服务端直接返回(返回字符串)
接下来就学习如何 设置状态码、Content-type、Body 及 相关信息
HTTP 的 POST 请求过程
注:
- 用 postman 发送一个 POST 请求 ->
- 通过 url 和 method 匹配路由,同时通过 body 携带数据 -> 服务端 ->
- 服务端返回成功状态 及 相关消息
接下来就学习如何 设置状态码、Content-type、Body 及 相关信息
# 2、res 返回 JSON 数据
TIP
- 定义路由:模拟获取评论列表 - 返回 JSON 数据
- 定义路由:模拟创建评论 - 返回 JSON 数据
- 未命中路由,返回 404 - 字符串形式
在 /src/index.js
中
const http = require("http");
const server = http.createServer((req, res) => {
const url = req.url;
const path = url.split("?")[0]; // /api/list
const method = req.method;
// 定义路由:模拟获取评论列表
if (path === "/api/list" && method === "GET") {
// res.end('<h1>这是评论列表的路由')
// 返回结果
const result = {
errno: 0,
data: [
{ username: "allen", content: "评论内容 1" },
{ username: "ibc", content: "评论内容 2" },
],
};
// 返回 JSON 数据
res.writeHead(200, { "Content-type": "application/json" });
// end() 方法中只能是字符串,需要通过 JSON.stringify() 转换
res.end(JSON.stringify(result)); // body
// 定义路由:模拟创建评论
} else if (path === "/api/create" && method === "POST") {
// res.end("<h1>这是模拟创建评论的路由 ! </h1>")
// 返回结果
const result = {
errno: 0,
message: "创建成功",
};
// 返回 JSON 数据
res.writeHead(200, { "Content-Type": "text/json" });
res.end(JSON.stringify(result));
} else {
// 未命中路由,Response 返回给前端 404(字符串形式)
res.writeHead(404, { "Content-Type": "text/plain" });
// 返回字符串
res.end("404 Not Found");
}
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
启动 node 服务
node .\src\index.js
在浏览器中输入 http://localhost:3000/api/list
获取评论列表,返回 JSON 数据
在 postman 中输入 http://localhost:3000/api/create
创建评论,返回 JSON 数据
在浏览器中输入任意不存在的 url 地址 http://localhost:3000/list/123456
返回 404 字符串
# 3、res 返回 HTML
TIP
在实际开发不会用到 “返回 HTML 格式”,NodeJS 本身做的是数据服务,而不是静态服务。因此如何返回 HTML、CSS、图片之类的不会涉及到,为了更好的学习和理解还是来研究下,知道即可。
方式方法 和 返回 JSON 数据类似
Content-Type: 'text/html'
res.end( ... )
- 浏览器会根据
Content-Type
识别出 HTML 格式
在 /src/index.js
中,将未命中路由的数据返回格式改成 HTML 格式
const http = require("http");
const server = http.createServer((req, res) => {
const url = req.url;
const path = url.split("?")[0]; // /api/list
const method = req.method;
// 定义路由:模拟获取评论列表
if (path === "/api/list" && method === "GET") {
// res.end('<h1>这是评论列表的路由')
// 返回结果
const result = {
errno: 0,
data: [
{ username: "allen", content: "评论内容 1" },
{ username: "ibc", content: "评论内容 2" },
],
};
// 返回 JSON 数据
res.writeHead(200, { "Content-type": "application/json" });
// end() 方法中只能是字符串,需要通过 JSON.stringify() 转换
res.end(JSON.stringify(result));
// 定义路由:模拟创建评论
} else if (path === "/api/create" && method === "POST") {
// res.end("<h1>这是模拟创建评论的路由 ! </h1>")
// 返回结果
const result = {
errno: 0,
message: "创建成功",
};
// 返回 JSON 数据
res.writeHead(200, { "Content-Type": "text/json" });
res.end(JSON.stringify(result));
} else {
// 未命中路由,Response 返回给前端 404(字符串形式)
// res.writeHead(404, { 'Content-Type': 'text/plain' })
// // 返回字符串
// res.end('404 Not Found')
// 将 404 返回字符串格式,改为返回 HTML 网页
res.writeHead(404, { "Content-Type": "text/html" });
res.end(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>404</title>
</head>
<body>
<h1>404 Not Found</h1>
</body>
</html>
`);
}
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
在浏览器中输入任意不存在的 url 地址 http://localhost:3000/list/123456
返回 404 HTML 格式
# 4、总结
TIP
- 使用 res 设置返回的状态码,Content-type ,Body
- 返回 JSON 数据、字符串、HTML 数据
在前端输入什么 url 及 参数 ,返回的结果 和 前端输入了什么没有关系,只跟服务端的控制有关系。
# 五、获取 Request Body
TIP
- 流 stream 的概念
- 如何获取 Request Body
- 使用 postman 测试
# 1、流(stream)的表现
TIP
- 观看视频时,一边加载一边观看
- 上传文件时,有进度条
- 网速较慢时,打开网页会先显示一部分,然后继续加载
以上的表现形式就很像流,类似水在水管中慢慢的流动。
- 视频:就类似于水,网络就类似水管,视频在水管中(网络上)慢慢的流动到我们本地来
- 上传文件:文件就是水,网络就是水管,文件顺着网络慢慢地流到服务端去
# 2、流(stream)解读
TIP
非常经典的关于流的图解
- 类似两个水桶:原始的源发地 -> 顺着水管 -> 流到目的地(从始发地 流到 目的地)
- 生活中这样的案例很多:抽水、鱼缸 ...
# 3、下载
TIP
下载的场景就是流的过程体现
- source 即:服务端,dest 即:客户端(前端)
- 下载的过程就是:从服务端 下载到 前端,类似水从 source 流到 dest 是一样的
- 下载过程,水 即:下载的数据,水管 即:网络(网络带宽不确定,有时快、有时慢)水管有粗有细,最终都会流过去只是时间长短问题
- 下载不仅指文件相关的,泛指所有的 Response Body(如:访问一个请求返回数据,可能是 JSON、字符串、HTML 等格式的数据)
# 3.1、浏览器能接收 流(stream)数据
TIP
- 服务端
res.end(...)
会自动以 流 的形式返回 - 浏览器会识别到 流,并持续接收信息(如果文件比较大,还会有进度条)这个过程不用我们管,但需要知道
- 待全部接收完,再做显示或处理(视频是一段一段的播放)
最终,服务器 到 浏览器是以流的形式返回去的。如下将 JSON 数据通过
res.end( ... )
返回给前端,这个过程就是以流(stream)的形式返回。数据量小时很快,大的时候会慢一些。
// 定义路由:模拟获取评论列表
if (path === "/api/list" && method === "GET") {
// res.end('<h1>这是评论列表的路由')
// 返回结果
const result = {
errno: 0,
data: [
{ username: "allen", content: "评论内容 1" },
{ username: "ibc", content: "评论内容 2" },
],
};
// 返回 JSON 数据
res.writeHead(200, { "Content-type": "application/json" });
// end() 方法中只能是字符串,需要通过 JSON.stringify() 转换
res.end(JSON.stringify(result)); // body
}
# 4、上传
TIP
上传不仅指上传大文件,泛指:创建评论信息(表单提交)、所有的浏览器前端往服务端提交的数据都属于上传
- source 即:客户端(前端),dest 即:服务端
- 水 即:上传的数据,水管 即:网络(带宽不确定)
- 上传不仅指大文件,泛指所有的 Request Body
# 4.1、服务端如何接收 流(stream)数据
TIP
- 前端使用 Ajax (或 Postman)提交数据 Request Body
- 服务端需要识别 流,并接收数据
- 还要知道何时才能接收完成
因此,我们接下来就要重点来学习 服务端如何识别 流,怎么知道流接收完成 ?
# 4.2、服务端如何接收 Request Body
先回顾一下 HTTP 的 POST 请求过程
注:
- 前端发送数据 ->
- 通过 url 和
method = POST
匹配到路由,同时通过 body 携带数据 - 但,body 数据还没有发送过,之前只是做了路由的匹配
- 如何发送 body 数据 ?如何设置
Content-type
?接下来会通过代码来实践
# 4.3、postman 上传数据 - 实践
TIP
在 /src/index.js
中,假设现在 postman 已经具备数据发送的能力,已发送了数据给 服务端,服务端该如何接收呢 ?
即:服务端识别到流(stream),并接受数据 !
在模拟创建评论路由中,才会接收前端发送过来的数据
const http = require("http");
const server = http.createServer((req, res) => {
// ... 省略部分代码
// 定义路由:模拟获取评论列表
if (path === "/api/list" && method === "GET") {
// ... 省略部分代码
// 定义路由:模拟创建评论
} else if (path === "/api/create" && method === "POST") {
// res.end("<h1>这是模拟创建评论的路由 ! </h1>")
// 定义接受前端数据
let bodyStr = "";
// 1、服务端怎么识别流,并接收数据
// chunk 即:流 的每一段数据(数据小一次就流过去了,数据大时就多流几次)
req.on("data", (chunk) => {
// 通过 chunk 来接受数据,每次流过来的数据都累加上(字符串格式)
bodyStr += chunk.toString();
});
// 2、服务端怎么知道流完了
req.on("end", () => {
// 打印输出从前端接收到的数据(输出的是一个字符串格式,因为上边用到的是 .toString() 方法累加的)
console.log("bodyStr 是服务端接收到的数据:", bodyStr);
// 先简单返回字符串
res.end("接收完成");
});
// 返回结果
// const result = {
// errno: 0,
// message: '创建成功'
// }
// // 返回 JSON 数据
// res.writeHead(200, { 'Content-Type': 'text/json' })
// res.end(JSON.stringify(result))
} else {
// 未命中路由,Response 返回给前端 404(字符串形式)
// ... 部分省略
}
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
启动 node 服务
node .\src\index.js
在 postman 中发送 POST 请求,在 body 中定义 JSON 格式的内容
注:发送数据时,还有一个 Content-Type: application/json
查看服务端接收到的数据
将前端接收数据,JSON 字符串转换为 “对象格式”
// 2、服务端怎么知道流完了
req.on("end", () => {
// 将接收到的 bodyStr JSON 字符串转换成 对象格式
const body = JSON.parse(bodyStr);
console.log("body 是服务端接收到的数据:", body);
// }
// 先简单返回字符串
res.end("接收完成");
});
转换数据格式后,再次用 postman 发送请求,服务端接收到的数据变为 “对象格式”
注:
- 我们目前是强制将 JSON 字符串变成了 对象格式,但这样并不靠谱
- 如果前端发送请求时(postman)
Content-Type
并没有设置为JSON
的数据格式,我们像上边代码中用JSON.parse(...)
方法来解析就会报错 - 因此,我们需要在服务端代码中先判断 前端传递的数据是不是 JSON 格式
在 /src/index.js
中判断前端传递的数据是不是 JSON 格式
const http = require("http");
const server = http.createServer((req, res) => {
const url = req.url;
const path = url.split("?")[0]; // /api/list
const method = req.method;
// 定义路由:模拟获取评论列表
if (path === "/api/list" && method === "GET") {
// res.end('<h1>这是评论列表的路由')
// 返回结果
const result = {
errno: 0,
data: [
{ username: "allen", content: "评论内容 1" },
{ username: "ibc", content: "评论内容 2" },
],
};
// 返回 JSON 数据
res.writeHead(200, { "Content-type": "application/json" });
// end() 方法中只能是字符串,需要通过 JSON.stringify() 转换
res.end(JSON.stringify(result));
// 定义路由:模拟创建评论
} else if (path === "/api/create" && method === "POST") {
// res.end("<h1>这是模拟创建评论的路由 ! </h1>")
// -------------------------------------
// 获取接收前端数据的类型
const reqType = req.headers["content-type"];
// 打印输出接收前端的数据类型
console.log(reqType);
// 定义接受前端数据
let bodyStr = "";
// 1、服务端怎么识别流,并接收数据
// chunk 即:流 的每一段数据(数据小一次就流过去了,数据大时就多流几次)
req.on("data", (chunk) => {
// 通过 chunk 来接受数据,每次流过来的数据都累加上
bodyStr += chunk.toString();
});
// 2、服务端怎么知道流完了
req.on("end", () => {
// 判断前端传来的数据是否是 JSON 格式(解析前要确保你的数据是标准的 JSON 格式,否则会解析出错)
if (reqType === "application/json") {
// 将接收到的 bodyStr JSON 字符串转换成 对象格式
const body = JSON.parse(bodyStr);
console.log("body 是服务端接收到的数据:", body);
}
// 先简单返回字符串
res.end("接收完成");
});
// 返回结果
// const result = {
// errno: 0,
// message: '创建成功'
// }
// // 返回 JSON 数据
// res.writeHead(200, { 'Content-Type': 'text/json' })
// res.end(JSON.stringify(result))
} else {
// 未命中路由,Response 返回给前端 404(字符串形式)
// res.writeHead(404, { 'Content-Type': 'text/plain' })
// // 返回字符串
// res.end('404 Not Found')
// 将 404 返回字符串格式,改为返回 HTML 网页
res.writeHead(404, { "Content-Type": "text/html" });
res.end(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>404</title>
</head>
<body>
<h1>404 Not Found</h1>
</body>
</html>
`);
}
});
// 监听 http 请求,默认 3000 端口
server.listen(3000);
console.log("http 请求已经被监听,3000 端口,请访问 http://localhost:3000");
TIP
启动 Node 服务后,在 postman 中发送请求,并观察服务端的打印输出信息
- 根据传入数据的类型判断是否要将 JSON 字符串转换为 对象格式
- 在 postman 中,如果请求传入的数据为 非 JSON 字符串,服务端就不解析
- 如果是 JSON 字符串格式,即 解析
大厂最新技术学习分享群
微信扫一扫进群,获取资料
X