7

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

 3 years ago
source link: https://fengxu.ink/2018/05/01/%E8%B6%85%E8%BD%BB%E9%87%8F%E7%BA%A7web%E6%A1%86%E6%9E%B6koa%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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。

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK