在一些新的 Web 框架中,经常会出现 “中间件” 的概念。 中间件是介于应用系统和系统软件之间的一类软件,它使用系 统软件提供的基础服务(功能),衔接网络上应用系统的各个 部分,达到资源共享、功能共享的目的。Express 框架中的中 间件也提供了类似的功能,其处于路由请求与主要逻辑处理中间,如图下图所示:
中间件可以做很多事情,例如对所有请求的日志进行记录,类似于记录日志,这种功能拥有一定的通用性,但又不是逻辑主程序的组成部分,这类通用且非业务逻辑的功能适合使用中问件进行处理。
不仅如此,中间件也可以用于身份验证。例如传统的用户登录状态的判定,需要在服务器要在服务器的Session中存放一些该用户的信息,用户在访问某些需耍权限控制的路由时通过Session 查看是否登录,该逻辑操作也可以使用中间件进行判断。中间件仅仅查询用户是否登(Session 是否存在且没有过期》,如果登录,则执行主程序,如果没有登录,则阻止用户,这样就无须在所有的路由中判断用户的状态了。
中间件的使用
使用Express路由功能编写代码,输出“Hello World!”, 代码如下:
var express = require ('express')
var app = express()
app.get(‘/’, function (req, res){
res.send ('Hello World!')
});
app.listen (3000)
编写一个简单的中间件,命名为checkUser, 提供用户请求信息的控制台打印功能。代码如下:
var express = require('express');
var app = express();
// 编写中间件,用于打印用户的头信息
var checkUser = function (req, res, next) {
console.log (req.headers);
next ();
}
// 全局使用中间件
app.use(checkuser);
app.get ('/', function (req, res){
res.send ('Hello World!')
});
app.listen(3000)
运行程序后,当用户进行路由访问后,自动打印用户的头信息。信息如下:
{
host: 'localhost:3000',
connection: 'keep-alive',
'sec-ch-ua': '"Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
'sec-ch-ua-platform': '"macOS"',
accept: 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
'sec-fetch-site': 'same-origin',
'sec-fetch-mode': 'no-cors',
'sec-fetch-dest': 'image',
referer: 'http://localhost:3000/',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
cookie: 'io=D35qxTrBXwl8fo3wAAAh'
}
针对上例,如果使用如下代码,则表示整个App中的所有路由均使用checkUser中间件
app.use(checkUser);
但是某些中间件并不需要所有的路由都使用,例如用户登录状志的检测,不是所有的页面都要进行检测,这类中间件可以通过路由指定。例如下面的代码,/user/:id路由中的所有请求URL 均调用checkLogin中间件,而其他的路由则不调用。
var express = require('express')
var app = express()
// 编写中间件,用于打印用户的头信息
var checkUser = function (req, res, next) {
console.log (req.headers);
next ();
}
// 全局使用中间件
app.use(checkUser);
// 新的中间件,用来检测用户的登录状态
var checkLogin = function (req, res, next) {
if (req.params.id === '1') {
console.log("用户登录成功");
next();
} else {
console.log("用户未登录");
res.send("error");
}
}
// 对于路由调用中间件
app.use('/user/:id', checkLogin);
// 路由定义
app.get ('/', function (req, res){
res.send('Hello World!')
});
// 新的路由定义
app.get('/user/:id', function (req, res) {
res.send("Hello" + req.params.id);
});
app.listen(3000);
上述代码定义了新的路由/user/:id,它通关GET方式传递一个id参数,该参数使用req.params.id来获取。
在中间件中,如果id不是字符串1(这里使用严格相等),则不将该请求发送至路由处理,而是直接返回error错误信息,并且在控制台打印用户登录失败提示。如果id参数是正确的,则执行next()将请求下发至路由处理,返回的信息是Hello字符串加上该参数。
运行代码,访问根目录/时打印用户的头信息,不执行checkLogin中间件。
如果访问http://localhost:3000/user/2,则返回用户请求头文件的同时打印登录失败的提示。
如果访问http://localhost:3000/user/1,则返回正常的内容,并且提示登录成功。