Koa2原理详解

ArtJCGL 7年前
   <h3><strong>1. Koa vs Express</strong></h3>    <p>Koa 是继 Express 之后,Node的又一主流Web开发框架。相比于Express,Koa只保留了核心的中间件处理逻辑,去掉了路由,模板,以及其他一些功能。</p>    <p>另一方面,在中间件的处理过程中,Koa和Express也有着一定区别,看下面例子:</p>    <pre>  <code class="language-java">// http style  http.createServer((req, res) => {    // ...  })    // express style  app.use((req, res, next) => {    // ...  })    // koa style  app.use((ctx, next) => {    // ...  })</code></pre>    <p>Node自带的 http 模块处理请求的时候,参数是一个 req 和 res ,分别为 http.IncomingMessage 和 http.ServerResponse 的实例。</p>    <p>Express对请求参数 req 和 res 的原型链进行了扩展,增强了 req 和 res 的行为。</p>    <p>而Koa并没有改变 req 和 res ,而是通过 req 和 res 封装了一个 ctx (context) 对象,进行后面的逻辑处理。</p>    <h3><strong>2. Koa基本组成</strong></h3>    <p>Koa源码非常精简,只有四个文件:</p>    <ul>     <li>application.js :Application(或Koa)负责管理中间件,以及处理请求</li>     <li>context.js :Context维护了一个请求的上下文环境</li>     <li>request.js :Request对 req 做了抽象和封装</li>     <li>response.js :Response对 res 做了抽象和封装</li>    </ul>    <h3><strong>3. Application</strong></h3>    <p>Application主要维护了中间件以及其它一些环境:</p>    <pre>  <code class="language-java">// application.js  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);    }      // ...  }</code></pre>    <p>通过 app.use(fn) 可以将 fn 添加到中间件列表 this.middleware 中。</p>    <p>app.listen 方法源码如下:</p>    <pre>  <code class="language-java">// application.js  listen() {    debug('listen');    const server = http.createServer(this.callback());    return server.listen.apply(server, arguments);  }</code></pre>    <p>首先会通过 this.callback 方法来返回一个函数作为 http.createServer 的回调函数,然后进行监听。我们已经知道, http.createServer 的回调函数接收两个参数: req 和 res ,下面来看 this.callback 的实现:</p>    <pre>  <code class="language-java">// application.js  callback() {    const fn = compose(this.middleware);      if (!this.listeners('error').length) this.on('error', this.onerror);      return (req, res) => {      res.statusCode = 404;      const ctx = this.createContext(req, res);      onFinished(res, ctx.onerror);      fn(ctx).then(() => respond(ctx)).catch(ctx.onerror);    };  }</code></pre>    <p>首先是将所有的中间件通过 compose 组合成一个函数 fn ,然后返回 http.createServer 所需要的回调函数。于是我们可以看到,当服务器收到一个请求的时候,会使用 req 和 res 通过 this.createContext 方法来创建一个上下文环境 ctx ,然后使用 fn 来进行中间件的逻辑处理。</p>    <h3><strong>4. Context</strong></h3>    <p>通过上面的分析,我们已经可以大概得知Koa处理请求的过程:当请求到来的时候,会通过 req 和 res 来创建一个 context (ctx) ,然后执行中间件。</p>    <p>事实上,在创建 context 的时候,还会同时创建 request 和 response ,通过下图可以比较直观地看到所有这些对象之间的关系。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/35ac7db9048692b4842b7130423f008f.png"></p>    <p>图中:</p>    <ul>     <li>最左边一列表示每个文件的导出对象</li>     <li>中间一列表示每个Koa应用及其维护的属性</li>     <li>右边两列表示对应每个请求所维护的一些列对象</li>     <li>黑色的线表示实例化</li>     <li>红色的线表示原型链</li>     <li>蓝色的线表示属性</li>    </ul>    <p>实际上, ctx 主要的功能是代理 request 和 response 的功能,提供了对 request 和 response 对象的便捷访问能力。在源码中,我们可以看到:</p>    <pre>  <code class="language-java">// context.js  delegate(proto, 'response')    .method('attachment')    // ...    .access('status')    // ...    .getter('writable');    delegate(proto, 'request')    .method('acceptsLanguages')    // ...    .access('querystring')    // ...    .getter('ip');</code></pre>    <p>这里使用了 delegates 模块来实现属性访问的代理。</p>    <p>简单来说,通过 delegate(proto, 'response') ,当访问 proto 的代理属性的时候,实际上是在访问 proto.response 的对应属性。</p>    <h3><strong>5. Request & Response</strong></h3>    <p>Request对 req 进行了抽象和封装,其中对于请求的url相关的处理如图:</p>    <pre>  <code class="language-java">┌────────────────────────────────────────────────────────┐  │                           href                         │  ├────────────────────────────┬───────────────────────────┤  │          origin            │     url / originalurl     │  ├──────────┬─────────────────┼──────────┬────────────────┤  │ protocol │      host       │   path   │     search     │  ├──────────├──────────┬──────┼──────────┼─┬──────────────┤  │          │ hostname │ port │          │?│ querystring  │  │          ├──────────┼──────┤          ├─┼──────────────┤  │          │          │      │          │ │              │  "  http:   │ host.com : 8080   /p/a/t/h  ?  query=string │  │          │          │      │          │ │              │  └──────────┴──────────┴──────┴──────────┴─┴──────────────┘</code></pre>    <p>Response对 res 进行了封装和抽象,这里不做赘述。</p>    <p> </p>    <p>来自:http://syaning.com/2016/11/08/koa2/</p>    <p> </p>