• Koa 源码解读


    介绍

    Koa 是一个新的 web 框架。通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。

    Koa 的代码短小精悍,那解读一个基础的 demo

    const Koa = require('koa');
    const app = new Koa();
    
    app.use(async ctx => {
      ctx.body = 'hello world';
    });
    
    app.listen(3000);
    
    

    const app = new Koa();

    初始化 app 所需参数,最重要的是创建 ctx,request 和 response,注意 Application 继承自 events。所以 app 可以 app.on('error') 来监听全局错误

    // koa/lib/application.js 
    const Emitter = require('events');
    module.exports = class Application extends Emitter {
    
      constructor() {
        super();
    
        this.proxy = false;
        this.middleware = [];
        this.subdomainOffset = 2;
        this.env = process.env.NODE_ENV || 'development';
        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(async ctx => { ctx.body = 'hello world'; });

    主要功能就是往 middleware 数组里加入所传函数

    // lib/application.js
    use (fn) {
      //... 
      this.middleware.push(fn);
      return this;
    }
    

    app.listen(3000);

    创建一个http server,监听传入端口号。执行一个叫 callback 的函数。

    //lib/application.js/listen
    
    listen(...args) {
      const server = http.createServer(this.callback());
      return server.listen(...args);
    }
    

    callback

    其主要功能就是返回了一个带着 req 和 res 实例的函数。

    //lib/application.js/callback
    callback() {
      /*
        const compose = require('koa-compose');
        这是一个神奇的方法,返回一个处理所有中间件的promise
      */
      const fn = compose(this.middleware);
      
      /*
        events 库自带的listenerCounter方法,返回正在监听的名为 error 的事件的监听器的数量。
        如果当前没有监听器,则框架自己设置一个error的监听器。用于格式化错误打印
      */
      if (!this.listenerCount('error')) this.on('error', this.onerror);
    
      const handleRequest = (req, res) => {
        // 创建 ctx 实例,并且在 handleRequest 里处理请求。
        const ctx = this.createContext(req, res);
        return this.handleRequest(ctx, fn);
      };
    
      return handleRequest;
    }
    

    createContext 是为创建 ctx 实例,由下面的代码可知 ctx 上挂载了 req,res,app。而为何 app 不挂 ctx 呢,这是因为 app 整个服务唯一,它与 ctx 是一对多关系,没法挂载某一个 ctx。

    
    createContext(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;
      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;
    }
    

    handleRequest 默认的 statusCode 是404。
    然后所有fnMiddleware执行完成的promise里执行respond函数,来处理最后的返回状态。fnMiddleware就是左边的const fn = compose(this.middleware)

    handleRequest(ctx, fnMiddleware) {
      const res = ctx.res;
      /*
        骚操作,默认状态404,当然,后续若你往body里赋值了什么会调用respond函数订正你真正的状态码。
      */
      res.statusCode = 404;
      const onerror = err => ctx.onerror(err);
      const handleResponse = () => respond(ctx);
      onFinished(res, onerror);
      // fnMiddleware,中间件顺序处理完内容后 去使用respond函数订正状态码和返回体等操作
      return fnMiddleware(ctx).then(handleResponse).catch(onerror);
    }
    
    

    compose 来源于 node_modules/koa-compose,可以把一堆 app.use 所传入的中间件函数,封装成一个 promise 暴露,让外界使用后可以顺序执行中间件,当然,需要手动调用 next 函数才能执行下一个中间件,这段代码非常精妙。

    
    function compose (middleware) {
      // ...
      
      return function (context, next) {
        // last called middleware #
        let index = -1
        return dispatch(0)
        
        // 递归执行所有的 middleware,返回 Promise 对象。
        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
          if (!fn) return Promise.resolve()
          try {
            return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
          } catch (err) {
            return Promise.reject(err)
          }
        }
      }
    }
    
    

    最后的响应函数是 respond,使用者往 ctx.res.body 里赋值的值,在这段函数里检测并最终给出请求的真实响应结果。

    
    function respond(ctx) {
      // allow bypassing koa
      if (false === ctx.respond) return;
    
      const res = ctx.res;
      if (!ctx.writable) return;
    
      let body = ctx.body;
      const code = ctx.status;
    
      // ignore body,如果是204或者304进入此逻辑
      if (statuses.empty[code]) {
        // strip headers
        ctx.body = null;
        return res.end();
      }
    
      // HEAD 方法只希望获取请求的响应头,常用来测试超链接的有效性,可用性和最近修改。
      if ('HEAD' == ctx.method) {
        if (!res.headersSent && isJSON(body)) {
          ctx.length = Buffer.byteLength(JSON.stringify(body));
        }
        return res.end();
      }
    
      // 默认的 404 状态,或者其他没写入 response body 的状态,进此逻辑
      if (null == body) {
        body = ctx.message || String(code);
        if (!res.headersSent) {
          ctx.type = 'text';
          ctx.length = Buffer.byteLength(body);
        }
        return res.end(body);
      }
    
      // 针对buffer,string,stream流类型做不通处理。
      if (Buffer.isBuffer(body)) return res.end(body);
      if ('string' == typeof body) return res.end(body);
      if (body instanceof Stream) return body.pipe(res);
    
      // body: json 如果 body 是 json 对象。则格式化一下输出
      body = JSON.stringify(body);
      if (!res.headersSent) {
        ctx.length = Buffer.byteLength(body);
      }
      res.end(body);
    }
    
    
  • 相关阅读:
    Unity 预处理命令
    Unity 2DSprite
    Unity 生命周期
    Unity 调用android插件
    Unity 关于属性的get/set
    代码的总体控制开关
    程序员怎么问问题?
    VCGLIB 的使用
    cuda实践(1)
    python之json文件解析
  • 原文地址:https://www.cnblogs.com/everlose/p/12846775.html
Copyright © 2020-2023  润新知