中间件
Express 是一个自身功能极简,完全是由路由和中间件构成一个的 web 开发框架:从本质上来说,一个 Express 应用就是在调用各种中间件。
中间件(Middleware) 就是一个回调函数,它可以接收参数 请求对象(req)), 响应对象( res), 和 web 应用中处于请求-响应循环流程中的中间件的函数,一般被命名为 next
的变量。
中间件的功能包括:
- 执行任何代码。
- 修改请求和响应对象。
- 结束响应。
- 调用堆栈中的下一个中间件。
如果当前中间件没有结束响应,则必须调用 next()
方法将控制权交给下一个中间件,否则请求就会挂起。
中间件相当于一个回调函数,可以对请求对象和响应对象进行修改,比如加入新的方法。
应用级中间件
应用级中间件绑定在应用对象app上,中间件函数作为app.use()
或者路由方法的回调函数。
1. 通过 app.use() 或者路由方法来绑定中间件
2. 路由方法的回调函数就是中间件
第一种方式 :定义访问日志中间件:
- 创建 accesslog.js,作为单独模块,定义中间件的代码
需要使用到 moment npm包
moment包的使用
moment().format('YYYY [escaped] YYYY'); 得到一个字符串
accesslog.js
const moment = require('moment');
const fs = require('fs');
const path = require('path');
module.exports = (req, res, next) => {
// 从请求报文中获取信息
const ip = req.ip.slice(7);
const method = req.method;
const url = req.url;
const dt = moment().format('YYYY-MM-DD HH:mm:ss');
// 拼接日志内容
const logMsg = `${ip} ${dt} ${method} ${url}\n`;
console.log(logMsg);
// 写入文件
fs.appendFile(path.resolve(__dirname, '../logs/access.log'), logMsg, err => {
if (err) {
throw err;
}
// 成功写入日志 放行
next();
});
};
- 在应用的入口文件挂载中间件
// 导入自定义中间件
const accessLog = require('./middleware/accesslog');
// 创建服务
const app = express();
// 在所有路由方法的前面
// 挂载访问日志中间件
app.use(accessLog);
第二种方式 :路由方法的回调函数就是中间件:
// 首页路由
app.get('/index', (req, res,next) => {
console.log('我也是中间件,路由回调就是中间件,我叫小乐子');
next();
});
第三种方式 :直接将中间件挂载到应用上:
app.use((req, res, next) => {
console.log('Hello, 我是中间件 Tom, How Are You?');
// 结束响应
//res.send('到这就结束了!');
// 执行下一个中间件
next();
});
第三种方式 :直接将中间件挂载到应用的指定路径上:
app.use('/login', (req, res, next) => {
post(); // 执行不存在的函数 报错
console.log('刷我的卡,我是中间件,我叫泰裤辣!');
next();
// console.log('next 后面的语句会执行');
});
多个中间件同时匹配,按照顺序响应,就像高速上的收费站,经过的中间件需要在处理后,进行放行Next();
错误处理中间件
错误处理中间件有 4 个参数,定义错误处理中间件时必须使用这 4 个参数。即使不需要
next
对象,也必须声明它,否则中间件会被识别为一个常规中间件,不能处理错误。错误处理中间件需要挂载在所有路由和中间件的后面,如果路由回调函数或前面的中间件中出现错误,会自动进入错误处理中间件!
错误处理中间件和其他中间件定义类似,只是要使用 4 个参数,而不是 3 个,其写法如下: (err, req, res, next)
。
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
定义一个错误处理中间件,响应500,且记录错误日志
创建文件 catcherror.js,作为一个模块,代码如下:
const moment = require('moment');
const fs = require('fs');
const path = require('path');
module.exports = (err, req, res, next) => {
// 从请求报文中获取信息
const ip = req.ip.slice(7);
const method = req.method;
const url = req.url;
const dt = moment().format('YYYY-MM-DD HH:mm:ss');
// 拼接日志内容
// err.stack 记录详细的错误信息
const errMsg = `${ip} ${dt} ${method} ${url} \n ${err.stack} \n\n\n\n`;
// 写入文件
fs.appendFile(path.resolve(__dirname, '../logs/error.log'), errMsg, err => {
if (err) {
throw err;
}
});
// 响应 500
res.status(500).send('<h1>500 服务器出错!</h1>');
};
在应用的入口文件挂载中间件
// 导入自定义中间件
const catchError = require('./middleware/catcherror');
// 创建服务
const app = express();
// 在所有路由方法的后面
// 挂载访问日志中间件
app.use(catchError);
托管静态文件
将静态资源文件所在的目录作为参数传递给 express.static
中间件就可以提供静态资源文件的访问了。例如,假设在 public
目录放置了图片、CSS 和 JavaScript 文件,你就可以:
app.use(express.static('public'));
如果你的静态资源存放在多个目录下面,你可以多次调用 express.static
中间件:
访问静态资源文件时,express.static
中间件会根据目录添加的顺序查找所需的文件。
app.use(express.static('public'));
app.use(express.static('files'));
如果你希望所有通过 express.static
访问的文件都存放在一个“虚拟(virtual)”目录(即目录根本不存在)下面,可以通过为静态资源目录指定一个挂载路径的方式来实现,如下所示:
app.use('/static', express.static('public'));
路由级中间件
路由级中间件和应用级中间件一样,只是它绑定的对象为 express.Router()
的返回值,路由级中间件可以实现路由的模块化。
可使用 express.Router
类创建模块化、可挂载的路由系统。Router
实例是一个完整的中间件和路由系统,因此常称其为一个 mini-app
。
下面的实例程序创建了一个路由模块,并加载了一个中间件,定义了一些路由,并且将它们挂载至应用的路径上。
实现路由模块化:
创建路由文件 index.js:
// 导入模块
const express = require('express');
// 创建路由对象
const route = express.Router();
// 路由
route.get('/', (req, res) => {
res.redirect('/index');
});
// 路由
route.get('/index', (req, res,next) => {
res.send(`
<h1>首页</h1>
<hr>
<a href="/login">登录</a>
`);
});
// 将路由对象作为暴露数据
module.exports = route;
创建路由文件 login.js
// 导入模块
const express = require('express');
// 创建路由对象
const route = express.Router();
// 路由
// 这里由于 挂载路由的方式是指定路径,所以只写/login/后边的 这里的根就是login文件夹
route.get('/', (req, res) => {
res.send(`
<h1>登录</h1>
<hr>
<form action="/login" method="post">
<input placeholder="请输入用户名" type="text" name="username">
<input placeholder="请输入密码" type="password" name="userpwd">
<button>提交</button>
</form>
`);
});
// 路由
route.post('/', (req, res) => {
res.send('<h2>提交成功!</h2>');
});
// 将路由对象作为暴露数据
module.exports = route;
在入口文件中,将路由文件挂载到应用上:
// 导入路由模块
const indexRouter = require('./routes/index');
const loginRouter = require('./routes/login');
// 挂载路由模块
app.use(indexRouter); //
app.use('/login', loginRouter); // 挂载路由 指定路径