zoukankan      html  css  js  c++  java
  • Koa

    前言

    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

  • 相关阅读:
    Extjs Ext.ux.IFrame的用法 以及父子窗口间函数相互调用
    Android ADB server didn't ACK * failed to start daemon * 简单有效的解决方案
    Java sun.misc.Unsafe类的学习笔记
    Java 并发编程学习笔记 理解CLH队列锁算法
    深入理解Java虚拟机 -- 读书笔记(1):JVM运行时数据区域
    Java并发编程学习笔记 深入理解volatile关键字的作用
    JVM Client Server启动设置
    双重检查锁定与延迟初始化
    Tomcat 添加为系统服务 开机自动启动
    配置TOMCAT 修改默认ROOT路径
  • 原文地址:https://www.cnblogs.com/chanwahfung/p/11427205.html
Copyright © 2011-2022 走看看