koa的使用和中间件

引言

近期重温了一下 node ,对 node 的 koajs 技术框架进行了学习,在此归纳了一些使用过程中的一些理解和 Koa 主要机制的解析。

原因

我们为什么要使用 Koa 框架呢,当我们使用 node 原生 http 模块编写后端服务尤其是在开发复杂的业务逻辑时,原生包往往难以维护后续需求迭代。此时我们可以选择一个合适的 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…

Koa 的目标是用更简单化、流程化、模块化的方式实现回调的业务逻辑,为了实现这个目的,Koa 使用了上下文和中间件的机制,为了更好地理解这两个机制,我们来简单手动实现一下。

上下文

koa 为了能够简化 API ,引入上下文 context 概念,将原始请求对象 req 和响应对象res封装并挂载到 context 上,并且在 context 上设置 getter 和 setter ,从而简化操作。

image

首先我们创建 request、response、context文件,并在其中加入 get 和 set 属性。

// 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 的基本使用,简单实现了一下中间件机制和上下文。以上就是本文的全部内容,希望对大家的学习有所帮助

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享