跳到主要内容

Express 中间件

示例引入

const express = require('express');
const app = express();
const port = 3000;

// 添加中间件,中间件的位置很重要!
app.use((req, res, next) => {
console.log('第一个公共中间件');
// 交出执行权,往后继续匹配执行
next();
});

app.get(
'/',
(req, res, next) => {
console.log('第一个自己的中间件');
next();
},
(req, res, next) => {
console.log('第二个自己的中间件');
next();
},
(req, res, next) => {
res.send('get /');
// 这里 next 可以调用之后写的中间件
next();
}
);

app.get('/about', (req, res) => {
res.send('get /about');
});

// 添加中间件,中间件的位置很重要!
app.use((req, res, next) => {
console.log('第二个公共中间件');
// 交出执行权,往后继续匹配执行
next();
});

app.post('/login', function (req, res) {
res.send('post /login');
});

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

概念解析

Express 中间件和 AOP 面相切面编程就是一个意思,都需要经过的一些步骤,不去修改自己的代码,以此来扩展或者处理一些功能。

AOP(Aspect Oriented Programming) 面相切面编程:

  • 将日志记录、性能统计、安全控制、事务处理、异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将他们独立到非指导业务逻辑的方法中,从而改变这些行为的时候不影响业务逻辑的代码
  • 利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率和可维护性

总结: 就是在现有代码程序中,在程序生命周期或者横向流程中加上/减去一个或者多个功能,不影响原来功能。

中间件函数

Express 的中间件

在 Express 中,中间件就是一个可以访问请求对象、响应对象和调用 next 方法的一个函数。

在中间件函数中可以执行以下任何任务:

  • 执行任何代码,添加属性或方法
  • 修改 request 和 response 响应对象
  • 结束请求响应周期
  • 调用下一个中间件

注意:如果当前的中间件功能没有结束请求-响应周期,则必须调用 next() 将控制权传递给下一个中间件功能。否则,该请求将被挂起。

Express 中间件的分类

在 Express 中应用程序可以使用以下类型的中间件:

  • 应用程序级别中间件
  • 路由器级别中间件
  • 错误处理中间件
  • 内置中间件
  • 第三方中间件

应用程序级别中间件

不关心请求路径:

app.use(function (req, res, next) {
console.log('time:', Date.now());
next();
});

限定请求路径:

app.get(
'/',
function (req, res, next) {
console.log('request type', req.method);
}
);

限定请求方法 + 请求路径:

app.get('/user/:id', (req, res) => {
res.send('user!');
});

多个处理函数:

app.get(
'/',
function (req, res, next) {
console.log('request url', req.originalUrl);
next();
},
function (req, res, next) {
console.log('request type', req.method);
next();
}
);

为同一个路径定义多个处理中间件:

app.get(
'/',
function (req, res, next) {
console.log('request url', req.originalUrl);
next();
},
function (req, res, next) {
console.log('request type', req.method);
res.send('hello world!');
next(); // 依然会执行之后的中间件
}
);

app.get('/', (req, res) => {
console.log('处理栈之外的中间件', req.method);
});

要从路由中间件堆栈中跳过其余中间件,请调用next('route')将控制权传递给下一条路由。

注意: next('route')仅在使用 app.METHID() 或者 router.METHOD() 函数加载的中间件函数中有效。

此示例显示了一个中间件子堆栈,该子堆栈处理对应 /user/:id 路径的 GET 请求。

app.get(
'/user/:id',
function (req, res, next) {
if (req.params.id === '0') next('route');
else next();
},
function (req, res, next) {
res.send('regular');
}
);

app.get('/user/:id', (req, res) => {
res.send('special');
});

中间件也可以在数组中声明为可重用。此示例显示了一个带有中间件子堆栈的数组,该子堆栈处理对/user/:id 路径的 GET 请求:

function logOriginalUrl(req, res, next) {
console.log('Request URL: ', req.originalUrl);
next();
}

function logMethod(req, res, next) {
console.log('Request Method: ', req.method);
next();
}

var logStuff = [logOriginalUrl, logMethod];

app.get('/user/:id', logStuff, function (req, res, next) {
res.send('user Info');
});

路由器级别中间件

const express = require('express');
const { getDb } = require('./db');

const router = express.Router();

router.get('/todos', async (req, res) => {
try {
const db = await getDb();
res.status(200).json(db.todos);
} catch (err) {
res.status(500).json({
err: err.message
});
}
});
module.exports = router;
// index.js
const express = require('express');

const router = require('./router');

const app = express();
const port = 3000;

app.use(router);
// 给路由限定访问前缀
// app.use('/abc', router);

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
// db.js
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);
const dbPath = path.join(__dirname, './db.json');

exports.getDb = async () => {
const data = await readFile(dbPath, 'utf8');
return JSON.parse(data);
};
// db.json
{
"todos": [
{
"id": 1,
"title": "吃饭"
},
{
"id": 2,
"title": "睡觉"
},
{
"id": 3,
"title": "写代码"
},
{
"id": 4,
"title": "看视频"
}
],
"users": []
}

错误处理中间件

以与其他中间件函数相同的方式定义错误处理中间件函数,除了使用四个参数而不是三个参数(特别是使用签名(err, req, res, next))之外:

// router.js
router.get('/todos', async (req, res, next) => {
try {
const db = await getDb();
res.status(200).json(db.todos);
} catch (err) {
// 如果将任何内容传递给 next() 函数,(字符串'route'除外),
// Express 都会将当前请求视为错误,并且将跳过所有剩余的无错误处理路由和中间件函数
// next(); // 往后匹配下一个中间件
// next(err); // 往后匹配当前中间件堆栈中的下一个
next(err); // 跳过所有剩余的无错误处理路由和中间件函数
}
});

// index.js
app.use(function(err, req, res, next) {
console.log(err.stack);
res.status(500).send({
error: err.stack
})
})

错误处理中间件始终带有4个参数,必须提供4个参数以将其标识为错误处理中间件函数。即使不需要使用该 next 对象,也必须指定它以维护签名。否则,该 next 对象将被解释为常规中间件。

处理 404

// 通常在所有的路由之后匹配处理 404 的内容
// 请求进来会从上往下一次匹配
app.use(function(req, res, next) {
res.status(404).send('404 Not Found.')
})

内置中间件

  • express.json():解析Content-Typeapplication/json格式的请求体。
  • express.urlencoded():解析Content-Typeapplication/www-form-urlencoded格式的请求体。
  • express.row():解析Content-Typeapplication/octet-stream格式的请求体。
  • express.text():解析Content-Typetext/plain格式的请求体。
  • express.static():托管静态资源文件

第三方中间件

早期的 Express 内置了很多中间件,后来 Express 在 4.x 之后移除了这些内置中间件,官方把这些功能性中间件以包的形式单独提供出来。这样做的目的是为了保持 Express 本身极简灵活的特性,开发人员可以根据自己的需要灵活使用:

Express 官方推荐中间件地址: http://expressjs.com/en/resources/middleware.html

const express = require('express');
// 日志中间件
const morgan = require('morgan');
const router = require('./router');

const app = express();
const port = 3000;

// app.use(morgan('tiny'));
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
app.use(router);
// 给路由限定访问前缀
// app.use('/abc', router);

// 通常在所有的路由之后匹配处理 404 的内容
// 请求进来会从上往下一次匹配
app.use(function(req, res, next) {
res.status(404).send('404 Not Found.')
})

app.use(function(err, req, res, next) {
console.log(err.stack);
res.status(500).send({
error: err.stack
})
})

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});