【笔记】Express学习笔记

前言

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

使用Express快速创建Web服务器或API接口服务器

安装

1
npm install express

创建服务器

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

// 监听GET请求
app.get("/", function(req, resp) {
// 响应数据
resp.send("ok");
});

// 监听POST请求
app.post("/", function(req, resp) {
// 响应数据
resp.send("ok");
});

// 启动服务器
app.listen(80, function() {
console.log("express server run at http://127.0.0.1");
});

处理GET请求参数

  • 默认情况下req.query是一个空对象
1
2
3
app.get("/", function(req, resp) {
console.log(req.query);
});

处理RESTful风格的GET请求参数

  • 通过req.params来接收RESTful风格的请求参数
1
2
3
app.get("/:key1/:key2", function(req, resp) {
console.log(req.params);
});
  • 如果发送的请求是http://127.0.0.1/value1/value2,通过req.params得到的响应如下
1
2
3
4
{
"key1": "value1",
"key2": "value2"
}

处理POST请求体数据

  • 如果不配置解析请求体数据的中间件,默认返回undefined
1
req.body

设置响应头

1
resp.setHeader("", "");

设置CORS响应头

设置允许跨域

允许指定域的跨域请求
1
resp.setHeader("Access-Control-Allow-Origin", "http://example.com");
允许任何域的跨域请求
1
resp.setHeader("Access-Control-Allow-Origin", "*");

设置允许额外的请求头

  • 默认情况下,为了设置CORS,客户端只能向服务端发送如下9个请求头:AcceptAccept-LanguageContent-LanguageDPRDownlinkSave-DataViewport-WidthWidthContent-Type
  • 如果需要设置其他请求头,需要在Access-Control-Allow-Headers进行声明
1
resp.setHeader("Access-Control-Allow-Headers", "X-Custom-Header");

设置允许的请求方式

允许客户端发送指定请求
  • 默认情况下,客户端只能向服务端发送GETPOSTHEAD请求
  • 如果需要发送其他请求,需要使用Access-Control-Allow-Methods来指明允许使用的请求方式
1
resp.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
允许客户端发送任何请求
1
resp.setHeader("Access-Control-Allow-Methods", "*");

创建静态资源服务器

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

./public:静态资源目录名

1
app.use(express.static("./public"));
1
2
+ public
- index.html
  • 此时访问http://127.0.0.1/index.html时就会从静态资源中获取了

指定访问路径前缀

  • 通常情况下访问前缀名与静态资源目录名相同

/public:访问路径前缀

1
app.use("/public", express.static("./public"));
  • 此时访问http://127.0.0.1/public/index.html时就会从静态资源中获取了

路由

  • 将请求类型和请求路径映射到指定处理函数,这种映射就是Express中的路由
  • 路由有优先级,会从上到下依次匹配,先被匹配成功的路由将会先执行处理函数

挂载到app对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const express = require("express");
const app = express();

// 挂载路由
app.get("/", function(req, resp) {
// 响应数据
resp.send("ok");
});
app.post("/", function(req, resp) {
// 响应数据
resp.send("ok");
});

app.listen(80, function() {
console.log("express server run at http://127.0.0.1");
});

模块化路由

  • 为了方便模块化管理,不建议直接将路由挂载到app对象,而是将路由抽离为单独的模块
  1. 创建路由模块对应的JS文件
  2. 调用express.Router()函数创建路由对象
  3. 由路由对象挂载具体路由
  4. 使用module.exports向外共享路由对象
  5. 使用app.use()注册路由模块
router.js
1
2
3
4
5
6
7
8
9
10
11
const express = require("express");
const router = express.Router();

router.get("/", function() {
resp.send("ok");
});
router.post("/", function() {
resp.send("ok");
});

module.exports = router;
index.js
1
2
3
4
5
6
7
8
9
10
11
const express = require("express");
const app = express();

// 导入路由模块
const router = require("./router");
// 注册路由模块
app.use(router);

app.listen(80, function() {
console.log("express server run at http://127.0.0.1");
});
  • 使用app.use()来注册全局中间件

指定访问路径前缀

/router:访问路径前缀

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
const express = require("express");
const app = express();

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

...

next();
}
// 将中间件函数设置为全局生效
app.use(func);

// 挂载路由
app.get("/", function(req, resp) {
// 响应数据
resp.send("ok");
});

app.listen(80, function() {
console.log("express server run at http://127.0.0.1");
});

简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require("express");
const app = express();

// 挂载全局生效中间件
app.use(function (req, resp, next) {

...

next();
});

// 挂载路由
app.get("/", function(req, resp) {
// 响应数据
resp.send("ok");
});

app.listen(80, function() {
console.log("express server run at http://127.0.0.1");
});

定义多个全局生效中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const express = require("express");
const app = express();

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

...

next();
}

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

// 挂载路由
app.get("/", function(req, resp) {
// 响应数据
resp.send("ok");
});

app.listen(80, function() {
console.log("express server run at http://127.0.0.1");
});

局部生效中间件

  • 将中间件函数传递给指定路由,使中间件函数只在指定路由上生效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require("express");
const app = express();

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

...

next();
}

// 挂载路由
app.get("/", func, function(req, resp) {
// 响应数据
resp.send("ok");
});

app.listen(80, function() {
console.log("express server run at http://127.0.0.1");
});

定义多个局部生效中间件

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

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

...

next();
}

// 挂载路由
app.get("/", func, func, function(req, resp) {
// 响应数据
resp.send("ok");
});
app.get("/", [func, func], function(req, resp) {
// 响应数据
resp.send("ok");
});

app.listen(80, function() {
console.log("express server run at http://127.0.0.1");
});

错误级别中间件

  • 错误级别中间件必须在挂载所有路由之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const express = require("express");
const app = express();

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

console.log(err.message);

next();
}

// 挂载路由
app.get("/", function(req, resp) {
// 响应数据
resp.send("ok");
});

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

app.listen(80, function() {
console.log("express server run at http://127.0.0.1");
});

Express内置中间件

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(模块名);

通过Express编写后端接口

  • 通过路由模块编写后端接口
router.js
1
2
3
4
5
6
7
8
9
10
11
12
const express = require("express");
const router = express.Router();

router.get("/子路径", function() {
resp.send({
"code": 200,
"msg": "ok",
"data": req.query
});
});

module.exports = router;
index.js
1
2
3
4
5
6
7
8
9
const express = require("express");
const app = express();

const router = require("./router.js");
app.use("/父路径", router);

app.listen(80, function() {
console.log("express server run at http://127.0.0.1");
});

请求的分类

简单请求

  • 请求方式为GETPOSTHEAD之一的请求
  • 请求头内只包含AcceptAccept-LanguageContent-LanguageDPRDownlinkSave-DataViewport-WidthWidthContent-Type之一的信息

预检请求

  • 请求方式为GETPOSTHEAD之外的请求

  • 请求头内包含除了AcceptAccept-LanguageContent-LanguageDPRDownlinkSave-DataViewport-WidthWidthContent-Type之外的信息

  • 客户端向服务断发送了application/json格式的数据

  • 预检请求与简单请求的区别:在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务端是否允许该实际请求,所以这一次OPTION请求被成为预检请求,服务端成功响应预检请求后,客户端才会发送真正的请求,并且携带真实数据

JSONP

  • 浏览器通过<script></script>标签的src属性向服务端请求数据,得到的是一个函数调用,这种请求方式就是JSONP
  • JSONP不属于真正的AJAX请求,因为没有使用XMLHttpRequest对象
  • JSONP仅支持GET请求方式

编写JSONP接口

  • 应当在配置cors中间件之前编写JSONP接口
1
2
3
4
5
6
7
8
9
10
app.get("/jsonp", function (req, resp) {
// 1. 获取客户端发送过来的回调函数的名字
const funcName = req.query.callback;
// 2. 得到要通过JSONP形式发送给客户端的数据对象
let data = {"key", "value"};
// 3. 根据得到的数据拼接一个函数调用的字符串
const scriptStr = `${funcName}(${JSON.stringify(data)})`;
// 4. 响应给前段的<script></script>标签进行解析
resp.send(scriptStr);
});

完成

参考文献

哔哩哔哩——黑马程序员