60

koa2 源码概览

 5 years ago
source link: https://github.com/warjiang/devcat/issues/7?amp%3Butm_medium=referral
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.

koa简单回顾

koa 内部对http请求、响应做了封装,同时提供了强大的中间件机制来方便开发者扩展koa的功能。一个koa版本的hello world如下所示:

bI7ram3.png!web

本地请求localhost:3000就可以看到 hello koa

从koa启动过程来看koa源码

从上述案例中可以看到,启动一个koa服务就是简单的new一个Koa实例,然后调用listen方法监听一个端口即可启动web服务。

下面笔者将顺着Koa的启动过程来简要分析一下Koa的源码。Koa源码目录结构如下所示:

bUVviaz.png!web

其中 application.js 就是今天源码分析的入口。在上述案例中,我们执行 new Koa() 实际上就是创建了一个 Application 实例,先来看看 Application 类中的构造函数:

NRvAJfV.png!web

在构造函数中,我们需要关注 contextrequestresponse 对象以及 middleware 数组,在构造函数中,仅仅完成了 Application 成员变量的初始化工作。

接着看 koa.listen() 函数,代码跳转到 Applicationlisten 函数:

URzIfiY.png!web

listen函数中基本就是启动服务器的常规操作,回顾下NodeJS中启动一个http的过程如下:

mamMjqz.png!web

那么 this.callback() 函数的返回结果就应该形如 (req,res)=>{// 处理逻辑} 。继续顺着 this.callback 继续往下看:

EZN7Zfu.png!web

this.callback 返回的 handleRequest 函数确实跟前面分析的一致。分析到这儿,也大概有个思路了,当用户请求到达时,会执行 handleRequest 函数对用户的请求进行处理,并给出对应的响应。继续追踪到 handleRequest 函数,函数中首先执行 createContext ,该函数的参数为 reqres ,继续追踪到 createContext 函数:

UJJr6rm.png!web

看完估计谁都得懵,官方给出的注释是 初始化一个新的上下文 ,我们来通过一张图来简单梳理下 contextrequestresponsethis 的关系:

B7faQ3F.png!web

可以猜测下 requestreponse 对象分别是对应 reqres 的封装。这里简单分析下 requestreq 的封装过程, request 对象的实现在 request.js 中,简单看下其中关于获取http头信息的方法:

bmMVjif.png!web

可以看到 reqeustheader 方法,实际上返回的结果是 this.req.headers ,而 createContext 函数中执行了 context.req = request.req = response.req = req; 。关于 Requestreq 的别的方法封装与获取header方法类似,这里不再赘述。在回到 contextrequestresponsethis 的关系图,从这张图可以看出,从 contextrequestresponse 中任意一个对象出发,都能访问其他的对象(猜测这里为了获取一定的灵活性吧)。最后函数返回的是 context 对象。

这里还要补充下关于 context 的代理方法,koa实现是使用了一个第三方库 delegates 来方便访问 requestresponse 对象上的属性。先简单给个 delegates 的案例。

UfMFR3n.png!web

从上述案例可以看出通过使用代理,可以将访问 obj 上的属性和方法都代理给 target 对象,这样当访问 obj.name 相当于访问 target.name ,访问 obj.sayHello() 相当于访问 target.sayHello() 。类似的, koacontext 也做了类似的工作。

3MfMVj7.png!web

这样当开发者访问 query 时,相当于访问了 request 对象上的 query 属性。到这儿,基本上已经把 createContext 方法看完了。下面继续回到 handleRequest 中,继续往下看到 this.handleRequest(ctx, fn); ,将刚创建好的 contextfn 传入到 handleRequest 函数中,再来看下这个 const fn = compose(this.middleware); ,this.middleware就是我们是用use函数构建的中间件:

iuInQ3V.png!web

当我们执行 app.use(xxx中间件) ,则会将 xxx中间件 push到 this.middleware 中。再来看看 compose 函数:

mIBV3eN.png!web

compose 函数通过函数 dispatch ,来递归执行 this.middleware 中的中间件,只要我们的中间件中继续执行 next() 方法就会递归下去,直到某个函数没有执行 next() 方法,则递归终止。给个简单的例子:

7NZNZnV.png!web

函数的执行结果为:

na26Bbz.png!web

这里我们任意构造了一个 ctx 传入到compose之后的函数 y 中,可以看到只要中间函数执行了next方法就会继续执行,否则函数则无法继续递归下去。另外一点主要注意的是,不同的中间函数之间是共享 ctx 的。只要任何对 ctx 的修改操作都是修改的最外层的 ctx 对象。

继续回到this.handleRequest函数:

RRf2Ubr.png!web

koa首先获取到了res对象,还记得之前的 context.res = res 么?并将默认res的状态码修改为404, onFinished 函数作用在于当请求完成后,用来释放资源。最后来看看 fnMiddleware(ctx).then(handleResponse).catch(onerror) ,这里的fnMiddleware就是之前的fn也就是compose(this.middleware),执行fnMiddleware(ctx),会将请求进来后创建的context对象先存到一个闭包中,后续通过compose函数内部的递归调用dispatch函数,将ctx和next依次传递给每个中间件函数。当执行完毕后,fnMiddleware(ctx)函数会返回一个promise对象,handleResponse函数在内部调用respond(ctx)函数,该函数内部根据不同的情况来做出对应的响应。最后把上面的分析过程整理如下:

3A7Bf2f.png!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK