常用中间件
koa
中间件的规范
- 是⼀个
async
函数 - 接收
ctx
和next
两个参数 - 任务结束需要执⾏
next
const middleWare = async (ctx, next) => { // 来到中间件,洋葱圈左边 next() // 进⼊其他中间件 // 再次来到中间件,洋葱圈右边};复制代码
中间件常⻅任务:
- 请求拦截
- 路由
- ⽇志
- 静态⽂件服务
路由中间件
路由其实就是对策略模式的一个实现,免去了大量的 if...else
。
// router.jsclass Router { constructor() { // 策略库 this.stack = [] } /** * 注册策略到策略库中 * @param {*} path 请求路径 * @param {*} method 请求方法 * @param {*} middleWare 中间件 */ register(path, method, middleWare) { let route = { path, method, middleWare } this.stack.push(route) } // 注册 get 请求 get(path, middleWare) { this.register(path, 'get', middleWare) } // 注册 post 请求 post(path, middleWare) { this.register(path, 'post', middleWare) } // 路由中间件 routes() { let _stack = this.stack // 返回的是一个中间件 return async function(ctx, next) { // 获取到上下文中的 url let currentPath = ctx.url // 声明一个策略 let route // 根据上下文中的 method 查找对应的策略 for (let i = 0; i < _stack.length; i++) { const item = _stack[i]; if (currentPath === item.path && item.method === ctx.method) { route = item.middleWare break } } // 如果取出的策略是一个函数,执行这个函数 if (typeof route === 'function') { route(ctx, next) return } // 进入下一个中间件 await next() } }}module.exports = Router复制代码
// index.jsconst MyKoa = require('./myKoa')const Router = require('./router')const app = new MyKoa()const router = new Router();router.get('/index', async ctx => { ctx.body = 'index page';});router.get('/post', async ctx => { ctx.body = 'post page'; });router.get('/list', async ctx => { ctx.body = 'list page'; });router.post('/index', async ctx => { ctx.body = 'post page'; });// 路由实例输出⽗中间件 router.routes()app.use(router.routes());app.listen(3000, () => { console.log('????????~ sever at 3000 ~~~');})复制代码
静态⽂件服务中间件
处理静态文件的请求。
- 配置绝对资源⽬录地址,默认为
static
- 获取⽂件或者⽬录信息
- 静态⽂件读取
- 返回
const fs = require("fs");const path = require("path");module.exports = (dirPath = "./public") => { return async (ctx, next) => { if (ctx.url.indexOf("/public") === 0) { // public开头 读取⽂件 const url = path.resolve(__dirname, dirPath); const fileBaseName = path.basename(url); const filepath = url + ctx.url.replace("/public", ""); console.log(filepath); // console.log(ctx.url,url, filepath, fileBaseName) try { stats = fs.statSync(filepath); if (stats.isDirectory()) { const dir = fs.readdirSync(filepath); // const const ret = ['<div style="padding-left:20px">']; dir.forEach(filename => { console.log(filename); // 简单认为不带⼩数点的格式,就是⽂件夹,实际应该⽤statSync if (filename.indexOf(".") > -1) { ret.push( `<p><a style="color:black" href="${ctx.url }/${filename}">${filename}</a></p>` ); } else { // ⽂件 ret.push( `<p><a href="${ctx.url}/${filename}">${filename}</a></p>` ); } }); ret.push("</div>"); ctx.body = ret.join(""); } else { console.log("⽂件"); const content = fs.readFileSync(filepath); ctx.body = content; } } catch (e) { // 报错了 ⽂件不存在 ctx.body = "404, not found"; } } else { // 否则不是静态资源,直接去下⼀个中间件 await next(); } };};复制代码
请求拦截中间件
请求拦截应⽤⾮常⼴泛:登录状态验证、CORS
头设置,⿊名单等。
本次实现一个⿊名单中存在的 ip
将被拒绝访问的功能。
module.exports = async function(ctx, next) { const { res, req } = ctx; const blackList = ['127.0.0.1']; const ip = getClientIP(req); if (blackList.includes(ip)) {//出现在⿊名单中将被拒绝 ctx.body = "not allowed"; } else { await next(); }};function getClientIP(req) { return ( req.headers["x-forwarded-for"] || // 判断是否有反向代理 IP req.connection.remoteAddress || // 判断 connection 的远程 IP req.socket.remoteAddress || // 判断后端的 socket 的 IP req.connection.socket.remoteAddress );}复制代码
BodyParser中间件
const middleWare = async (ctx, next) => { console.log('????????~ body-parser'); const req = ctx.request.req let reqData = []; let size = 0; await new Promise((resolve, reject) => { req.on('data', data => { console.log('????????~ req on', data); reqData.push(data); size += data.length }) req.on('end', () => { console.log('????????~ end'); const data = Buffer.concat(reqData, size) console.log('????????~ data:', size, data.toString()); ctx.request.body = data.toString() resolve(); }) }) await next()};复制代码