【笔记】Express学习笔记

前言

Fast, unopinionated, minimalist web framework for Node.js(官网

安装

1
npm install express

创建Web服务器

80:指定端口号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 引入模块
const express = require("express");

// 定义端口号
const port = 80;

// 创建对象
const app = express();

// 定义路由,处理请求
app.get("/", function (req, resp) {
// 接收请求,返回响应
resp.send("");
});

// 绑定端口,启动服务器
app.listen(port, "0.0.0.0", function () {
console.log(`http://127.0.0.1:${port}`);
});

请求

获取请求参数

query

request
1
GET http://127.0.0.1:80/?id=1
1
2
3
4
app.get("/", function (req, resp) {
const query = req.query;
console.log(query["id"]); // "1"
});

path

request
1
GET http://127.0.0.1:80/1
1
2
3
4
app.get("/:id", function (req, resp) {
const path = req.params;
console.log(path["id"]); // "1"
});

body

request
1
2
3
4
5
6
POST http://127.0.0.1:80/
Content-Type: application/json

{
id: "1"
}
1
2
3
4
app.post("/", function (req, resp) {
const body = req.body;
console.log(body.id); // "1"
});

响应

设置响应头

1
resp.setHeader("Content-Type", "application/json");

创建静态资源服务器

  • 指定一个目录,将这个目录作为静态资源目录,所有静态资源的请求可以直接被访问
  • 如果先后指定了多个相同层级的相同静态资源,此时先托管的静态资源路径会生效,后托管的静态资源路径不会生效

./public:静态资源目录名

1
app.use(express.static("./public"));
1
2
+ public
- index.html

指定访问路径前缀

1
app.use("/public", express.static("./public"));

路由

  • 将请求类型和请求路径映射到指定处理函数,这种映射就是Express中的路由
  • 路由有优先级,会从上到下依次匹配,先被匹配成功的路由将会先执行处理函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 引入模块
const express = require("express");

// 定义端口号
const port = 80;

// 创建对象
const app = express();
const router = express.Router();

// 定义路由,处理请求
router.get("/", function (req, resp) {
// 接收请求,返回响应
resp.send("");
});
// 注册路由
app.use(router);

// 绑定端口,启动服务器
app.listen(port, "0.0.0.0", function () {
console.log(`http://127.0.0.1:${port}`);
});

指定访问路径前缀

1
app.use("/router", router);

中间件(Middleware)

  • 中间件指业务流程的中间处理环节

  • Express中的中间件本质上就是一个特殊的处理函数

  • 中间件处理函数的形参必须包含next,同时函数体中结尾必须包含next()函数

  • 中间件处理函数在做完处理后,会交给下一个中间件或路由进行进一步处理

  • 除了错误级别的中间件,其他的中间件一定要在路由之前配置

  • 中间件的分类

    • 应用级别的中间件:绑定到app实例上的中间件
    • 路由级别的中间件:绑定到express.Router()实例上的中间件
    • 错误级别的中间件:错误级别的中间件是一种特殊的全局中间件,专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
      • 错误级别的中间件函数必须有4个形参,形参顺序从前到后分别是errreqrespnext

全局生效中间件

  • 当客户端发起任何请求,到达服务器时,都会触发全局生效中间件
  • 通过app.use()可以设置一个中间件处理函数作为全局生效中间件
  • 多个中间件之间共用一份reqresp对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 引入模块
const express = require("express");

// 定义端口号
const port = 80;

// 创建对象
const app = express();

// 定义一个中间件函数
const fn = function (req, resp, next) {

...

next();
}

// 挂载全局生效中间件
app.use(fn);

// 定义路由,处理请求
app.get("/", function (req, resp) {
// 接收请求,返回响应
resp.send("");
});

// 绑定端口,启动服务器
app.listen(port, "0.0.0.0", function () {
console.log(`http://127.0.0.1:${port}`);
});

定义多个全局生效中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 引入模块
const express = require("express");

// 定义端口号
const port = 80;

// 创建对象
const app = express();

// 定义一个中间件函数
const fn = function (req, resp, next) {

...

next();
}

// 挂载全局生效中间件
app.use(fn);
app.use(fn);

// 定义路由,处理请求
app.get("/", function (req, resp) {
// 接收请求,返回响应
resp.send("");
});

// 绑定端口,启动服务器
app.listen(port, "0.0.0.0", function () {
console.log(`http://127.0.0.1:${port}`);
});

局部生效中间件

  • 将中间件函数传递给指定路由,使中间件函数只在指定路由上生效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 引入模块
const express = require("express");

// 定义端口号
const port = 80;

// 创建对象
const app = express();

// 定义一个中间件函数
const fn = function (req, resp, next) {

...

next();
}

// 定义路由,处理请求
app.get("/", fn, function (req, resp) {
// 接收请求,返回响应
resp.send("");
});

// 绑定端口,启动服务器
app.listen(port, "0.0.0.0", function () {
console.log(`http://127.0.0.1:${port}`);
});

定义多个局部生效中间件

  • 直接传递多个中间件函数传递给路由
  • 也可以将多个中间件函数作为数组传递给路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 引入模块
const express = require("express");

// 定义端口号
const port = 80;

// 创建对象
const app = express();

// 定义一个中间件函数
const fn = function (req, resp, next) {

...

next();
}

// 定义路由,处理请求
app.get("/", fn, fn, function (req, resp) {
// 接收请求,返回响应
resp.send("");
});
app.get("/", [fn, fn], function (req, resp) {
// 接收请求,返回响应
resp.send("");
});

// 绑定端口,启动服务器
app.listen(port, "0.0.0.0", function () {
console.log(`http://127.0.0.1:${port}`);
});

错误级别中间件

  • 错误级别中间件必须在挂载所有路由之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 引入模块
const express = require("express");

// 定义端口号
const port = 80;

// 创建对象
const app = express();

// 定义一个中间件函数
const fn = function (err, req, resp, next) {

console.log(err.message);

next();
}

// 定义路由,处理请求
app.get("/", function (req, resp) {
// 接收请求,返回响应
resp.send("");
});

// 挂载错误级别中间件
app.use(fn);

// 绑定端口,启动服务器
app.listen(port, "0.0.0.0", function () {
console.log(`http://127.0.0.1:${port}`);
});

Express内置中间件

express.static

  • 快速托管静态资源
  • 无兼容性
1
app.use(express.static());

express.json

  • 解析JSON格式的请求体数据
  • 仅在4.16.0及以后的版本可用
1
app.use(express.json());

express.urlencoded

  • 解析url-encoded格式的请求体数据
  • 仅在4.16.0及以后的版本可用
1
app.use(express.urlencoded({ extended: false }));

第三方中间件

body-parser

  • express内置中间件就是根据body-parser进一步封装的
安装
1
npm install body-parser
引入
1
const parser = require("body-parser");
注册并使用
1
app.use(parser.urlencoded({ extended: false }));

cors

  • 通过cors模块解决跨域问题
安装
1
npm install cors
引入
1
const cors = require("cors");
注册并使用
1
app.use(cors());

express-session进行身份认证

  • 通过express-session进行身份认证
  • 在不跨域的情况下推荐使用session进行身份认证
安装
1
npm install express-session
引入
1
const session = require("express-session");
注册

secret:随意指定一个字符串作为Session的加密密钥

1
2
3
4
5
app.use(session({
secret: "",
resave: false,
saveUninitialized: true
}));
通过req.session对象中存取数据
  • 通常在登录成功后将用户登录状态和用户信息存储在session中
1
req.session = {};
销毁req.session对象中存储的数据
  • 通常在退出登录后,将当前请求的用户对应的session清除
1
req.session.destroy();

jsonwebtoken和express-jwt

  • 使用jsonwebtoken模块将JSON字符串生成JWT加密字符串Token
  • 使用express-jwt模块将JWT加密字符串Token还原成JSON对象
安装
1
npm install jsonwebtoken express-jwt
引入
1
2
const jwt = require("jsonwebtoken");
const expressJWT = require("express-jwt");
定义一个用于加解密的密钥
1
const secretKey = "";
加密
  • 将用户登录信息加密成为token,并响应给前端

<user>:需要存储的用户信息
secretKey:指定密钥
expiresIn: "":指定有效时间

s:秒
m:分钟
h:小时

1
2
3
4
5
app.post("/login", function (req, resp) {
resp.send({
token: jwt.sign(<user>, secretKey, {expiresIn: "30s"})
});
});
解密
注册中间件
  • 将express-jwt对象作为中间件注册
1
app.use(expressJWT({secret: secretKey}));
  • 通过unless()指定不需要鉴权的请求路径,可以使用通配符
1
app.use(expressJWT({secret: secretKey}).unless({path: [/^\/dist/]}));
获取解密后的对象
  • 中间件在得到加密的Token后,会自动解密,并将解密后的对象挂载在req.user对象上
1
2
3
app.post("/login", function (req, resp) {
console.log(req.user);
});
异常处理
  • 如果Token是无效或者过期的,会报错:UnauthorizedError,通过全局异常处理函数可以捕获异常
1
2
3
4
5
6
7
8
app.use(function (err, req, resp, next) {
if (err.name === "UnauthorizedError") {
return resp.send({
code: "401",
msg: "鉴权失败"
});
}
});

自定义中间件

  • 示例:通过自定义中间件实现express.urlencoded中间件的功能
    • 定义一个自定义中间件
    • 监听data事件
      • 在中间件中,需要坚挺req对象的data事件,来获取客户端发送到服务端的数据
      • 如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务端,所以data事件可能会触发多次,每次触发data事件️,获取到的数据都只是完整数据的一部分,需要手动对接收到的数据进行拼接
    • 监听end事件
      • 当数据发送完毕,会自动触发req对象的end事件
    • 使用Nodejs的内置模块querystring将请求体字符串转换成JS对象
      • 如果请求体字符串中包含url编码的中文字符,也会自动解码
    • 将解析后的数据挂载为req.body
    • next()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const querystring = require("querystring");

app.use(function (req, resp, next) {
let data = "";
req.on("data", function (chunk) {
data += chunk;
});
req.on("end", function () {
console.log(data);
data = querystring.parse(data);
req.body = data;
next();
});
});

将自定义中间件封装为模块

  • 封装为模块
模块名.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const querystring = require("querystring");

const 模块名 = function (req, resp, next) {
let data = "";
req.on("data", function (chunk) {
data += chunk;
});
req.on("end", function () {
console.log(data);
data = querystring.parse(data);
req.body = data;
next();
});
}

module.exports = 模块名;
  • 引入模块
index.js
1
2
const 模块名 = require("./模块名.js");
app.use(模块名);

完成

参考文献

哔哩哔哩——黑马程序员