Koa - 中間件

来源:https://www.cnblogs.com/chanwahfung/archive/2019/08/31/11427205.html
-Advertisement-
Play Games

前言 Koa 應用程式是一個包含一組中間件函數的對象,它是按照類似堆棧的方式組織和執行的。 當一個中間件調用 next() 則該函數暫停並將控制傳遞給定義的下一個中間件。當在下游沒有更多的中間件執行後,堆棧將展開並且每個中間件恢復執行其上游行為。 以上兩句話,是我在官方文檔中找到其對 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

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • DDL的全稱Data Definition Language,即數據定義語言 DDL的語法有:create、alter、drop、rename、truncate。對此做一個詳細的解釋: create (創建) create 可以創建資料庫 create 可以創建表格 創建表格的語法:方括弧的表示可以 ...
  • https://www.oracle.com/technetwork/cn/topics/index-088165-zhs.html 下載地址Orion是Oracle提供的IO性能測試工具,運行該工具不需要安裝oracle database軟體或創建資料庫。 它可以模擬Oracle資料庫的IO負載,... ...
  • 最近遇到了這個案例,官方文檔已有詳盡的分析、介紹,特轉載在此,方便以後查看! Full UNDO Tablespace In 10gR2 and above (文檔 ID 413732.1) 轉到底部 In this Document Symptoms Changes Cause Solution ... ...
  • 寫了一個bat命令,定期去清理一些SQL Server的Dump文件,然後配置成SQL Server作業,作業執行時報許可權錯誤,具體錯誤信息如下所示: Message Executed as user: NT Service\SQLSERVERAGENT. The process could not... ...
  • DJI_Mobile_SDK是大疆為開發者提供的開發無人機應用的開發介面,可以實現對無人機飛行的控制,也可以利用無人機相機完成一些視覺任務。目前網上的開發教程主要集中於DJI 開發者社區,網上的資源非常少。廢話不多說~~,現在將在Android項目中學習到的東西總結一下。 使用大疆無人機做電腦視覺 ...
  • 好了,的所有的基礎知識已經準備完畢了,現在開始製作引導頁。這個引導頁需要一個HTML文件,JS文件,一個CSS文件。在HBuilderX中根目錄下添加“Guid.html”,在JS文件夾添加“myth.js”,在CSS文件夾下添加“myth.css”。 一、myth.js文件 該文件是個插件,對常用 ...
  • 最近因為換工作的原因沒有寫博客,現在慢慢穩定了,我準備寫一些關於Android 進階的文章,也是為了督促自己學習,大家一起進步! 今天詳細的分析一下Android APP架構之一:MVC ### MVC簡介 >[MVC](https://baike.baidu.com/item/MVC)全名是Mod ...
  • react+react-native+react-navigation+react-redux+react-native-swiper+rnPop等技術開發仿微信聊天室RN_ChatRoom,實現了app全屏啟動頁、popupWindow彈窗菜單、消息觸摸列表、發送消息、表情(動圖),圖片預覽,拍攝... ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...