• koa源码分析


    最近项目要使用koa,所以提前学习一下,顺便看了koa框架的源码.

    注:源码是koa2.x

    koa的源码很简洁,关键代码只有4个文件,当然还包括一些依赖npm包

    const Koa = require('koa');                                                        
                                                                                     
    const app = new Koa();                                                             
                                                                                     
    app.use(async (ctx, next) => {                                                     
      await next();                                                                    
      ctx.type = 'text/html';                                                          
      ctx.body = '<h1>Hello, koa2!</h2>';                                                          
    });                                                                              
                                                                                     
    app.listen(3000);                                                                
    console.log('app started at port 3000....');

    我们由上面的代码开始深入到koa的源码:

    application.js文件:

    上面代码的开头引入koa框架,接着const app = new Koa();创建koa实例app,koa的构造函数很简单,如下:

      constructor() {                                                                   
        super();                                                                        
                                                                                        
        this.proxy = false;                                                             
        this.middleware = [];      //用来存放中间件                                                         
        this.subdomainOffset = 2;                                                      
        this.env = process.env.NODE_ENV || 'development';   //运行环境                              
        this.context = Object.create(context);           //创建context对象                               
        this.request = Object.create(request);           //创建request对象               
        this.response = Object.create(response);         //创建response对象            
      }
    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 || '-');                                 
        this.middleware.push(fn);                                                    
        return this;                                                                 
      } 

     use函数首先判断参数是否是函数,不是就报错,然后判断这个函数是否是generator函数,如果是generator则需要转换一下,通过两个判断后,将这个函数push到middleware数组中保存.最后返回this(也是app实例)

    listen函数:
    isten() {                                                                     
        debug('listen');                                                             
        const server = http.createServer(this.callback());                           
        return server.listen.apply(server, arguments);                               
      }

    listen函数里面调用http.createServe()创建http服务,关键是参数this.callback(),看一下代码:

    callback() {                                                                   
        const fn = compose(this.middleware);                                         
                                                                                     
        if (!this.listeners('error').length) this.on('error', this.onerror);         
                                                                                     
        const handleRequest = (req, res) => {                                        
          res.statusCode = 404;                                                      
          const ctx = this.createContext(req, res);                                  
          const onerror = err => ctx.onerror(err);                                   
          const handleResponse = () => respond(ctx);                                 
          onFinished(res, onerror);                                                  
          return fn(ctx).then(handleResponse).catch(onerror);                        
        };                                                                           
                                                                                     
        return handleRequest;                                                        
      }

    首先,把所有middleware进行了组合,使用了koa-compose,我们也不用去管他的内部实现,简单来说就是返回了一个promise数组的递归调用。

    这里的 const fn = compose(this.middleware);对应的调用代码如下:(查看koa-compose源码):

     return function (context, next) {                                              
        // last called middleware #                                                  
        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                                     
          if (!fn) return Promise.resolve()                                          
          try {                                                                      
            return Promise.resolve(fn(context, function next () {                    
              return dispatch(i + 1)                                                 
            }))                                                                      
          } catch (err) {                                                            
            return Promise.reject(err)                                               
          }                                                                          
        }                                                                            
      }                                                                              
    }

    return前面的作用是做参数检测,return后面也就是上面贴出来的代码才是我们要关注的:

     运行流程:

    i = 0 ==> index = 0 ==> fn = middleware[0] ==> return Promise.resolve(//如果fn里面存在await,则functions next()函数会被掉用, 则运行return dispatch(i + 1),以此类推)
    知道中间件(当然一般指最后一个中间件)中不存在await或者所有的中间件都加载完毕(i === middleware.length) ,compose函数则最终返回.

    例如:

    const Koa = require('koa') 
    const app = new Koa() 
    app.use(async function (ctx, next) {                                             
      		console.log('>> one');                                                         
      		await next();                                                                  
     		 console.log('<< one');                                                         
    }); 
    app.use(ctx => {                                                                   
     		ctx.body='hello world gcy';                                                    
    })
    

    上面代码注册了两个中间件,经过const fn = compose(this.middleware)后返回:

    fn的形式如下:

    Promise.resolve(function(ctx) {
    console.log('>> one');
    return Promose.resolve(function(ctx){
    ctx.body='hello world gcy';
    }).then(() => {
     console.log('<< one');
    })
    })
    

    执行结果: 后台输出:>> one  -- > 页面输出:hello world gcy --> 后台输出:<< one

    接下来:

     if (!this.listeners('error').length) this.on('error', this.onerror);  
    

    在处理http请求之前,koa会注册一个默认的错误处理函数,但我们每次http请求错误实际上是由ctx.onerror处理的:

    const onerror = err => ctx.onerror(err);
    onFinished(res, onerror);
    fn(ctx).then(() => respond(ctx)).catch(onerror)
    

    ctx.onFinished 是确保一个流在关闭、完成和报错时都会执行相应的回调函数。onerror 就是我们http请求错误处理函数.
    我们看看这个匿名函数,把http code默认设置为404,接着利用createContext函数把node返回的req和res进行了组合创建出context
    来看下createContext函数:

    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.cookies = new Cookies(req, res, {
        keys: this.keys,
        secure: request.secure
      });
      request.ip = request.ips[0] || req.socket.remoteAddress || '';
      context.accept = request.accept = accepts(req);
      context.state = {};
      return context;
    }
    

    这里面都是一堆的组合和赋值,context,request, response相互挂载
    值得注意的是:context.req/context.res 和context.request/context.response的区别,context.req/context.res代表nodejs的req和res对象,而context.request/context.response是koa的request和response对象
    这个函数最后返回context,然后传入fn函数,此时fn函数被执行.
    callback()函数中调用的respond函数里面不过是一些收尾工作,例如判断http code为空如何输出啦,http method是head如何输出啦,body返回是流或json时如何输出。

    context.js
    delegate(proto, 'request') //Request相关方法委托,从而让context作为调用入口
    onerror(err) //中间件执行过程中异常处理逻辑

    request.js,response.js

    分别对res和req进行了抽象和封装

     

  • 相关阅读:
    Java 使用Calendar类输出指定年份和月份的日历
    ioc aop
    多线程下单例模式:懒加载(延迟加载)和即时加载
    Java生产环境下性能监控与调优详解
    springboot + zipkin + mysql
    springboot + zipkin(brave-okhttp实现)
    springboot启动方式
    OpenResty实现限流的几种方式
    RocketMQ核心技术精讲与高并发抗压实战
    codis 使用
  • 原文地址:https://www.cnblogs.com/y-yxh/p/6879451.html
Copyright © 2020-2023  润新知