前言

Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。

当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。

以上两句话,是我在官方文档中找到其对 Koa 中间件的描述。

在Koa中,中间件是一个很有意思的设计,它处于request和response中间,被用来实现某种功能。像上篇文章所使用的 koa-router 、koa-bodyparser 等都是中间件。

可能有些人喜欢把中间件理解为插件,但我觉得它们两者并不是同一种概念的东西。插件像是一个独立的工具,而中间件更像是流水线,将加工好的材料继续传递下一个流水线。所以中间件给我的感觉更灵活,可以像零件一样自由组合。

单看中间件有堆栈执行顺序的特点,两者就出现质的区别。

中间件的概念

这张图是 Koa 中间件执行顺序的图示,被称为“洋葱模型”。

中间件按照栈结构的方式来执行,有“先进后出“的特点。

一段简单的代码来理解上图:

app.use(async (ctx, next)=
console.log('--> 1')
next()
console.log('<-- 1')
}) app.use(async (ctx, next)=>{
console.log('--> 2')
//这里有一段异步操作
await new Promise((resolve)=>{
....
})
await next()
console.log('<-- 2')
}) app.use(async (ctx, next)=>{
console.log('--> 3')
next()
console.log('<-- 3')
})
app.use(async (ctx, next)=>{
console.log('--> 4')
})

当我们运行这段代码时,得到以下结果

--> 1

--> 2

--> 3

--> 4

<-- 3

<-- 2

<-- 1

中间件通过调用 next 一层层执行下去,直到没有执行权可以继续传递后,在以冒泡的形式原路返回,并执行 next 函数之后的行为。可以看到 1 第一个进去,却是最后一个出来,也体现出中间件栈执行顺序的特点。

在第二个中间件有一段异步操作,所以要加上await,让执行顺序按照预期去进行,否则可能会出现一些小问题。

中间件的使用方式

1.应用中间件

const Koa = require('koa');
const Router = require('koa-router'); const app = new Koa();
const router = new Router();
app.use(async (ctx,next)=>{
console.log(new Date());
await next();
})
router.get('/', function (ctx, next) {
ctx.body="Hello koa";
})
router.get('/news',(ctx,next)=>{
ctx.body="新闻页面"
});
app.use(router.routes()); //作用:启动路由
app.use(router.allowedMethods()); //作用: 当请求出错时的处理逻辑
app.listen(3000,()=>{
console.log('starting at port 3000');
});

2.路由中间件

router.get('/', async(ctx, next)=>{
console.log(1)
next()
})
router.get('/', function (ctx) {
ctx.body="Hello koa";
})

3.错误处理中间件

app.use(async (ctx,next)=> {
next();
if(ctx.status==404){
ctx.status = 404;
ctx.body="这是一个404页面"
}
});

4.第三方中间件

const bodyParser = require('koa-bodyparser');
app.use(bodyParser());

实现验证token中间件

实现一个基于 jsonwebtoken 验证token的中间件,这个中间件由两个文件组成 extractors.js 、index.js,并放到check-jwt文件夹下。

生成token

const Router = require('koa-router')
const route = new Router()
const jwt = require('jsonwebtoken') route.get('/getToken', async (ctx)=>{
let {name,id} = ctx.query
if(!name && !id){
ctx.body = {
msg:'不合法',
code:0
}
return
}
//生成token
let token = jwt.sign({name,id},'secret',{ expiresIn: '1h' })
ctx.body = {
token: token,
code:1
}
}) module.exports = route

使用 jwt.sign 生成token:

第一个参数为token中携带的信息;

第二个参数为key标识(解密时需要传入该标识);

第三个为可选配置选项,这里我设置过期时间为一小时;

详细用法可以到npm上查看。

使用中间件

app.js:

const {checkJwt,extractors} = require('./check-jwt')

app.use(checkJwt({
jwtFromRequest: extractors.fromBodyField('token'),
 secretOrKeyL: 'secret',
safetyRoutes: ['/user/getToken']
}))
  是否必选 接收类型 备注
jwtFromRequest 函数
默认验证 header 的 authorization
extractors提供的提取函数,支持get、post、header方式提取
这些函数都接收一个字符串参数(需要提取的key)
对应函数:

fromUrlQueryParameter、
fromBodyField、
fromHeader
 
secretOrKey 字符串 与生成token时传入的标识保持一致
safetyRoutes 数组 不需要验证的路由
 
 
 
 
 
 
 
 
 
 

使用该中间件后,会对每个路由都进行验证

路由中获取token解密的信息

route.get('/getUser', async ctx=>{
let {name, id} = ctx.payload
ctx.body = {
id,
name,
code:1
}
})

通过ctx.payload来获取解密的信息

实现代码

extractors.js 工具函数(用于提取token)

let extractors = {}

extractors.fromHeader = function(header_name='authorization'){
return function(ctx){
let token = null,
request = ctx.request;
if (request.header[header_name]) {
token = header_name === 'authorization' ?
request.header[header_name].replace('Bearer ', '') :
request.header[header_name];
}else{
ctx.body = {
msg: `${header_name} 不合法`,
code: 0
}
}
return token;
}
} extractors.fromUrlQueryParameter = function(param_name){
return function(ctx){
let token = null,
request = ctx.request;
if (request.query[param_name] && Object.prototype.hasOwnProperty.call(request.query, param_name)) {
token = request.query[param_name];
}else{
ctx.body = {
msg: `${param_name} 不合法`,
code: 0
}
}
return token;
}
} extractors.fromBodyField = function(field_name){
return function(ctx){
let token = null,
request = ctx.request;
if (request.body[field_name] && Object.prototype.hasOwnProperty.call(request.body, field_name)) {
token = request.body[field_name];
}else{
ctx.body = {
msg: `${field_name} 不合法`,
code: 0
}
}
return token;
}
} module.exports = extractors

index.js  验证token

const jwt = require('jsonwebtoken')
const extractors = require('./extractors') /**
*
* @param {object} options
* @param {function} jwtFromRequest
* @param {array} safetyRoutes
* @param {string} secretOrKey
*/ function checkJwt({jwtFromRequest,safetyRoutes,secretOrKey}={}){
return async function(ctx,next){
if(typeof safetyRoutes !== 'undefined'){
let url = ctx.request.url
//对安全的路由 不验证token
if(Array.isArray(safetyRoutes)){
for (let i = 0, len = safetyRoutes.length; i < len; i++) {
let route = safetyRoutes[i],
reg = new RegExp(`^${route}`);

//若匹配到当前路由 则直接跳过 不开启验证
if(reg.test(url)){
return await next()
}
}
}else{
throw new TypeError('safetyRoute 接收类型为数组')
}
}
if(typeof secretOrKey === 'undefined'){
throw new Error('secretOrKey 为空')
}
if(typeof jwtFromRequest === 'undefined'){
jwtFromRequest = extractors.fromHeader()
}
let token = jwtFromRequest(ctx)
if(token){
//token验证
let err = await new Promise(resolve=>{
jwt.verify(token, secretOrKey,function(err,payload){
if(!err){
//将token解码后的内容 添加到上下文
ctx.payload = payload
}
resolve(err)
})
})
if(err){
ctx.body = {
msg: err.message === 'jwt expired' ? 'token 过期' : 'token 出错',
err,
code:0
}
return
}
await next()
}
}
} module.exports = {
checkJwt,
extractors
}

Demo: https://gitee.com/ChanWahFung/koa-demo

最新文章

  1. HotApp小程序服务范围资质查询器
  2. 《jQuery判断radio、checkbox、select 是否选中和设置选中问题以及获取选中值》总结
  3. Java 后台获取当前时间
  4. 利用bootstrap的modal组件自定义alert,confirm和modal对话框
  5. Lua面向对象设计
  6. jekyll中文乱码问题
  7. 青蛙的烦恼(dp好题)
  8. HTML基本操作
  9. vim 7.4 编译安装
  10. Android 应用程序的组成部分
  11. 在IIS上发布一个WebService,再发布一个网站调用这个WebService(实例)
  12. js中数组对象根据内容查找符合的第一个对象
  13. ElasticSearch和ElasticSearch Head环境搭建和数据模拟
  14. Spring boot JPA 用自定义主键策略 生成自定义主键ID
  15. 【读书笔记】iOS-优化内存
  16. Android DatePickerDialog 使用方法
  17. 关于MySql悲观锁与乐观锁
  18. iOS开发:代码通用性以及其规范 第二篇(猜想iOS中实现TableView内部设计思路(附代码),以类似的思想实现一个通用的进度条)
  19. Python中的高级turtle(海龟)作图(续)
  20. Git commit comment 汇总标准

热门文章

  1. Python 基础之re 模块
  2. 3. 彤哥说netty系列之Java BIO NIO AIO进化史
  3. nyoj 33-蛇形填数 (循环,模拟)
  4. nyoj 75-日期计算 (闰年与平年的判断)
  5. python:爬虫0
  6. Docker从入门到掉坑(三):容器太多,操作好麻烦
  7. JAVA语 言 的 特 点
  8. 带你涨姿势的认识一下 Kafka 消费者
  9. Android中常见的设计模式
  10. 使用Docker搭建maven私服 及常规使用方法