koa的使用和中間件

引言

近期重溫了一下 node ,對 node 的 koajs 技術框架進行了學習,在此概括了一些使用過程當中的一些理解和 Koa 主要機制的解析。node

緣由

咱們爲何要使用 Koa 框架呢,當咱們使用 node 原生 http 模塊編寫後端服務尤爲是在開發複雜的業務邏輯時,原生包每每難以維護後續需求迭代。此時咱們能夠選擇一個合適的 Web 框架來支持開發。web

概述

Koa 致力於成爲 web 應用和 API 開發領域中的一個更小、更富有表現力、更健壯的基石。Koa 是由 Express 原班人馬(TJ Holowaychuk)開發,經過使用組合各類更高級的異步流程控制中間件,來免除繁瑣的回調函數嵌套,並極大提高常見錯誤的處理效率。Koa 做爲 Web 開發微框架,能夠用於傳統 Web 應用開發、做爲服務器端接口、做爲獨立的API層、網關等等場景。後端

基本使用

咱們來看一個基本 Demo 的使用:設計模式

const Koa = require("koa");
const app = new Koa();
app.use(async (ctx, next) => {
  // 咱們在這裏定義一個日誌,咱們的業務邏輯能夠寫在next()以前和以後
  // 在返回時間以前設置一個時間戳
  const start = Date.now();
  // next()進入下一個use中
  await next();
  // 在返回時間以後再返回一個時間戳
  const end = Date.now();
  console.log(`請求${ctx.url} 耗時${parseInt(end - start)}ms`);
});
app.use((ctx, next) => {
  // 咱們等待120毫秒後返回 此處能夠作相關業務邏輯處理
  const expire = Date.now() + 120;
  while (Date.now() < expire) {
    ctx.body = {
      name: "myName",
    };
  }
});
app.listen(3000, () => {
  console.log("start...");
});
複製代碼

打印結果:數組

image

執行流程爲:服務器

image

對於 Koa 框架,咱們這裏不過多講解官方的文檔和源碼的解析,有興趣的同窗能夠在 Koa 官網進行了解或者看一下這篇對 Koa 源碼解析的文章 jelly.jd.com/article/5f8…markdown

Koa 的目標是用更簡單化、流程化、模塊化的方式實現回調的業務邏輯,爲了實現這個目的,Koa 使用了上下文和中間件的機制,爲了更好地理解這兩個機制,咱們來簡單手動實現一下。app

上下文

koa 爲了可以簡化 API ,引入上下文 context 概念,將原始請求對象 req 和響應對象res封裝並掛載到 context 上,而且在 context 上設置 getter 和 setter ,從而簡化操做。框架

image

首先咱們建立 request、response、context文件,並在其中加入 get 和 set 屬性。koa

// request.js
module.exports ={
    get url(){
        return this.req.url
    },
    get method(){
        return this.req.method.toLowerCase()
    }
    // ...
}
複製代碼
// response.js
module.exports = {
    get body(){
        return this._body
    },
    set body(val){
        this._body = val
    }
    // ...
}
複製代碼
// context.js
module.exports = {
    get url() {
        return this.request.url
    },
    get body() {
        return this.response.body
    },
    set body(val){
        this.response.body = val
    },
    get method() {
        return this.request.method
    }
    // ...
}
複製代碼

咱們將 ctx 把 res、req、response、request 進行相互掛載起來,進行相互關聯上下文。

createContext(req, res) {
  const ctx = Object.create(context);
  ctx.request = Object.create(request);
  ctx.response = Object.create(response);
  ctx.req = ctx.request.req = req;
  ctx.res = ctx.response.res = res;
  return ctx;
}
複製代碼

而後在createServer中將req,res傳入createContext函數建立上下文。

const server = http.createServer(async (req, res) => {
  //...
  let ctx = this.createContext(req, res);
  //...
複製代碼

中間件

那麼什麼是中間件呢,中間件是 Koa 框架的核心擴展機制,主要用於抽象 HTTP 請求過程,在單一請求響應過程當中加入中間件,能夠更好地應對複雜的業務邏輯。在 HTTP 請求的過程當中,中間件至關於一層層的濾網,每一箇中間件在HTTP處理過程當中經過改寫請求和響應數據、狀態,實現相應業務邏輯。Koa 中間件機制就是函數式組合概念,將一組須要順序執行的函數複合爲一個函數,外層函數的參數實際是內層函數的返回值。咱們能夠將中間件視爲設計模式中的責任鏈模式。

經過以上執行流程,咱們能夠看出中間件的執行順序,流程是一層層的打開,而後一層層的閉合,就像剝洋蔥同樣,早期的 Python 爲這種執行方式起了一個很好聽的名字,洋蔥模型。

image

實現這種洋蔥圈機制的中間件有不少種實現思路,咱們用遞歸的思想來實現一下。 咱們首先聲明一個 compose 合成函數,該函數返回一個函數的組合並傳入咱們的上下文 ctx 對象,而後聲明須要每次執行的異步函數並返回函數中返回第一層的執行承諾,咱們判斷若是取到的本層函數爲空那麼返回一個空的承諾,不然返回執行本次方法自己的執行函數並傳入下一個函數的執行,咱們來看一下代碼

// myKoa
compose(middlewares) {
    return function (ctx) {
      return dispatch(0);
      // i=>表示返回哪一個異步函數
      function dispatch(i) {
        // 取出第一個洋蔥圈
        let fn = middlewares[i];
        // 下面的洋蔥圈自己都是異步函數,咱們只能返回承諾Promise對象,經過地櫃方式
        if (!fn) {
          return Promise.resolve();
        }
        return Promise.resolve(
          // 執行本次方法自己 每次執行的放入的next對象是下一層執行承諾
          fn(ctx, function next() {
            // 返回下一層洋蔥圈 執行下一層
            return dispatch(i + 1);
          })
        );
      }
    };
  }
複製代碼

咱們在 constructor 構造方法中初始化一個數組 middlewares

// myKoa
  constructor() {
    this.middlewares = [];
  }
複製代碼

而後在 createServer 中初始化 compose 組合函數,而後傳入 ctx 上下文對象並執行該組合函數。

// myKoa
// ...
const server = http.createServer(async (req, res) => {
  //...
  const fn = this.compose(this.middlewares);
  await fn(ctx);
  //...
複製代碼

最後咱們在 use 方法中接受每次傳過來的函數並將其存到 middlewares數組中

// myKoa
use(middleware) {
  this.middlewares.push(middleware);
}
複製代碼

咱們測試一下:

const MyKoa = require("./myKoa");
const app = new MyKoa();
const delay = () => new Promise((resolve) => setTimeout(() => resolve(), 2000));
app.use(async (ctx, next) => {
  ctx.body = "1";
  await next();
  ctx.body += "5";
});
app.use(async (ctx, next) => {
  ctx.body += "2";
  await delay();
  await next();
  ctx.body += "4";
});
app.use(async (ctx, next) => {
  ctx.body += "3";
});
app.listen(3000, () => {
  console.log("start...");
});
複製代碼

image

myKoa.js完整源碼;

const http = require("http");
const context = require("./context");
const request = require("./request");
const response = require("./response");
class MyKoa {
  constructor() {
    this.middlewares = [];
  }
  listen(...args) {
    // 建立http server
    const server = http.createServer(async (req, res) => {
      // 建立上下文
      let ctx = this.createContext(req, res);
      const fn = this.compose(this.middlewares);
      await fn(ctx);
      // 數據響應
      res.end(ctx.body);
    });
    // 啓動監聽
    server.listen(...args);
  }
  use(middleware) {
    this.middlewares.push(middleware);
  }
  createContext(req, res) {
    const ctx = Object.create(context);
    ctx.request = Object.create(request);
    ctx.response = Object.create(response);
    ctx.req = ctx.request.req = req;
    ctx.res = ctx.response.res = res;
    return ctx;
  }

  compose(middlewares) {
    return function (ctx) {
      return dispatch(0);
      // i=>表示返回哪一個異步函數
      function dispatch(i) {
        // 取出第一個洋蔥圈
        let fn = middlewares[i];
        // 下面的洋蔥圈自己都是異步函數,咱們只能返回承諾Promise對象,經過遞歸方式
        if (!fn) {
          return Promise.resolve();
        }
        return Promise.resolve(
          // 執行本次方法自己 每次執行的放入的next對象是下一層執行承諾
          fn(ctx, function next() {
            // 返回下一層洋蔥圈 執行下一層
            return dispatch(i + 1);
          })
        );
      }
    };
  }
}

module.exports = MyKoa;

複製代碼

以上咱們實現了一個簡單的中間件洋蔥圈模型機制。Koa 中間件能夠對請求和響應同時進行攔截,這是Web框架裏少有的功能,其餘 Web 框架只對請求進行攔截,不對響應進行攔截好比 Express,因此在中間件機制上,Koa 是佔有優點的。固然 Koa 的這種機制相比於 Express 會更復雜一些,中間件數量增多疊加起來以後,請求和響應的攔截就會造成相似回形針同樣的調用。

image

總結

koa2 框架將會是是 node.js Web開發方向大勢所趨的普及框架,本文和你們一塊兒看了一下 Koa 的基本使用,簡單實現了一下中間件機制和上下文。以上就是本文的所有內容,但願對你們的學習有所幫助

相關文章
相關標籤/搜索