koa 中间件洋葱模型源码分析

中间件基本使用

app.use(async(ctx,next)=>{
    ctx.state={
        username:'jeff'
    };
    await next();
    ...
})

app.use(async(ctx,next)=>{
    ctx.state={
        username:'jeff2'
    };
    await next();
    ...
})

Koa 初始化应用实例
const app = new Koa()

  • 为 app 实例添加 context、request、response、middleware 等属性
  constructor(options) {
    super();
    this.middleware = [];
    // 每一个 app 实例,都有下面三个对象的实例
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
    if (util.inspect.custom) {
      this[util.inspect.custom] = this.inspect;
    }
  }

app.use() 添加中间件

  use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    if (isGeneratorFunction(fn)) {
      deprecate('Support for generators will be removed in v3. ' +
                'See the documentation for examples of how to convert old middleware ' +
                'https://github.com/koajs/koa/blob/master/docs/migration.md');
      fn = convert(fn);
    }
    debug('use %s', fn._name || fn.name || '-');
    // 直接存入到 middleware 数组中,后续统一处理
    this.middleware.push(fn);
    return this;
  }

app.listen() 监听

  listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }

this.callback()

  • 在 Node 的 http 模块,对于每一个请求,都会走到回调函数 callback 中去。所以这个 callback 是用于处理实际请求的
  callback() {
    // 包装所有的中间件,返回一个可执行的函数。koa-compose 实现了洋葱圈模型
    const fn = compose(this.middleware);
    if (!this.listenerCount('error')) this.on('error', this.onerror);
    
    // 实际触发的函数
    const handleRequest = (req, res) => {
      // req res 是 node http模块原生请求参数
      const ctx = this.createContext(req, res);
      // 将创建的 ctx 返回,传给所有中间件,作为整个请求的上下文
      return this.handleRequest(ctx, fn);
    };
    return handleRequest;
  }

this.createContext

  • 明确 context 挂载的内容,这个 context 是每一次请求都会生成的
  • 每次请求都生成避免了全局污染
this.createContex  createContext(req, res) {
    // 每一个请求对应一个 ctx、request、response、req、res
    const context = Object.create(this.context);
    const request = context.request = Object.create(this.request);
    const response = context.response = Object.create(this.response);
    context.app = request.app = response.app = this;
    // 挂载 node 原生请求参数 req res 到 context、request、response 上
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    request.ctx = response.ctx = context;
    request.response = response;
    response.request = request;
    context.originalUrl = request.originalUrl = req.url;
    context.state = {};
    return context;
  }

compose 实现洋葱圈模型

  • 遍历中间件列表,将下一个中间件的执行函数函数作为参数传递给next,通过递归的方式,在执行当前中间件的next时,进入下一个中间件
function compose (middleware) {
  // 类型检查
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }
	
  //返回的洋葱模型的可执行函数	
  return function (context, next) {
    // 上一次被执行的中间件
    let index = -1
    //自执行,返回中间件执行的结果
    return dispatch(0)
    
    function dispatch (i /*表示预期想要执行哪个中间件*/) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      //递归临界点
      if (i === middleware.length) fn = next
      // 没有fn的话,直接返回一个已经reolved的Promise对象
      if (!fn) return Promise.resolve()
      try {
        /*
        原代码是一行,为了方便理解拆成了三行
        //获取下一个中间件的执行函数
        const next = dispatch.bind(null, i + 1);
        //执行本次中间件函数,执行next就会进行下一个中间件next的分配和执行
        const fnResult = fn(context, next);
        //将此每次中间件执行结果Promise化
        return Promise.resolve(fnResult);
        */
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

this.handleRequest

  • fnMiddleware为执行中间件递归处理的函数,返回Promise
  handleRequest(ctx, fnMiddleware) {
    const res = ctx.res;
    res.statusCode = 404;
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    // 执行 compose 后的中间件函数,最后执行 respond
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值