超轻量级web框架koa源码阅读

1,368 阅读6分钟
原文链接: fengxu.ink

koa是一个非常轻量的web框架,里面除了ctx和middleware之外什么都没有,甚至连最基本的router功能都需要通过安装其他中间件来实现。不过虽然简单,但是它却非常强大,仅仅依靠中间件机制就可以构建完整的web服务。而koa的源码同样很简洁,基础代码只有不到2000行,非常适合阅读学习。

koa的源码直接从github获取,本文采用目前最新的2.5.1版本。

代码结构

第一眼看到koa的源码时候我真的懵了,反复确认没有看错之后才确信,koa源码只有四个文件–application.js,context.js,request.js,response.js,位于项目的lib文件夹下。而且一看文件名基本上就能猜到每个文件是做什么的了,接下来就是打开查看里面的内容。

koa基本启动流程

首先看package.json里面的main,可以知道application.js是入口文件,里面是一个继承自event模块下的Emitter类的Application类,我们使用koa时候创建的app实例就是在这里定义的。

分析一个类自然要先看它的构造函数,里面重点的就是定义了一个数组middleware,还有三个属性context,request,response分别为三个对象,而这三个对象就是在对应的其他三个文件中定义的。在此我们先不看另外的文件,想想我们使用koa的时候,创建app实例之后,接下来就是use各种中间件了,所以直接看use方法。

use接收一个中间件函数作为参数,首先做类型校验,如果传入的是generator,在koa2中会先通过convert进行转换(此处是为了兼容koa1,后续版本将移除),最后其实只做了一件事,就是把这个函数push到middleware数组中去。use方法最后会返回this,也就是koa实例本身,这就意味着我们可以实现链式调用。

设置好中间件,我们开启koa服务的最后一步就是调用listen方法设置监听端口,接下来就看一下listen方法的实现。我们会发现listen更简单,只有两行,其实什么额外的事情也没做,只是调用了node原生的http模块下面的createServer方法创建服务,listen方法设置监听,仅此而已。我们都知道http的createServer需要传入一个函数,这个函数在koa里面是通过调用callback方法返回的,接下来看callback的实现。

callback里面首先使用compose把所有的中间件变成一个函数(compose的实现同样后续会详细分析),这里会首先调用Emitter中的listenerCount方法判断是否有error事件的监听器,如果没有会为error事件注册默认的事件监听方法onerror,之后就是定义我们要的那个传入createServer的函数了。这个函数接收req和res两个参数,之后,koa会对其做一个处理:通过调用createContext方法把req和res封装成我们熟悉的ctx对象(createContext具体做了哪些工作接下来会说),然后把ctx和之前处理好的中间件函数fnMiddleware传入handleRequest方法中。

handleRequest中首先先取出res,先把状态置为404,然后对执行中间件后的成功和失败状态注册方法,失败调用ctx.onerror捕获异常,成功调用respond方法处理结果。这里还是用了onFinished模块,onFinished能确保一个流在关闭、完成和报错时都会执行相应的回调函数,这里把我们的异常处理函数传入用以处理错误信息。而respond方法,里面做的,就是读取ctx信息,把数据写入res中并响应请求。至此,整个流程就完成了。

ctx的创建

createContext里面的代码其实特别简单,就是创建了三个对象context,request,response,然后把使用ctx时候的各种东西都挂到context对象上,这样我们就可以在ctx上面获取到req,res等等各种信息了。创建context,request,response对象时候用到了当前app类里面的三个对象,它们是通过从外部三个文件中引入的对象来创建的,所以接下来就看一下这三个文件中都有什么。

这三个文件导出的都是对象,在context中,只做了一些基础方法的定义,剩下的一切属性方法全部都使用delegate代理到request和response属性的访问了。而前面我们已经知道,context上面的request和response就是通过另外的两个文件中的对象创建得到的。而这两个文件的内容就更加简洁了,都是我们平时使用时候访问的属性和方法,通过getter和setter的方式来控制上面的req和res从而实现对实际请求和响应操作的封装。于是整个koa核心的四个文件就彻底完成了。

compose实现原理与中间件机制

首先做一些合法性校验,重点在于最后的返回结果是一个函数,这个函数就是我们上面的fnMiddleware,它同样也有context和next两个参数,在其内部采用index变量记录当前处理到哪个中间件,然后从第一个开始调用dispatch方法。首先会判断当前传入参数与index的关系,如果在一个中间件内多次调用next,会出现参数小于index的情况,此时就会报错。之后把当前中间件从数组中取出来,每次执行时会把ctx和next传入,next中调用dispatch,参数为下一个位置,这样就会按顺序把中间件添加进来,最后当i等于中间件数组长度时候,也就是没有其他中间件了,那么执行一开始传入的next参数,如果fn不存在,返回空的promise。当中心执行完,也就是前一个中间件的next执行完,自然会触发await向下执行,之后执行权会反向顺序返回,最终组合的结果就是先从外向里,再从里向外,就是我们熟知的洋葱圈模型。

错误处理机制

koa的错误处理机制也很有特点,我们只要监听koa实例的error事件,就可以统一处理所有的错误。我们在前面提到过,调用fnMiddleware失败后会被统一的onerror方法捕获,这个方法是对应到ctx上的onerror方法,我们来看一下里面的实现,里面非常重要的一行就是this.app.emit('error', err, this);,由于我们的koa是继承自event,所以可以派发出一个error事件,我们只要处理该事件即可。而前面在中间件处理中,如果发生错误就会reject,自然可以被catch捕获到。

以上,就是koa基本核心模块的流程,原理很简单,但是配合各种中间件,koa完全可以实现一个功能完整web server。

本文原创,愿意分享但转载请提前告知,更多文章查看我的主页,感谢阅读,如错误欢迎指正。