前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Koa源码阅读

Koa源码阅读

作者头像
小刀c
发布2022-08-16 15:10:44
5100
发布2022-08-16 15:10:44
举报
文章被收录于专栏:cc log

toc

Koa源码阅读

Koa 在众多NodeJs框架中,以短小精悍而著称,核心代码只有大约570行,非常适合源码阅读。

实际上核心来说,Koa主要是两块

  • 中间件系统
  • 对请求结构封装为更为易用的ctx对象。

本文就核心阅读中间件的源码。

Koa使用

中间件可以理解为插件,对于Koa来说,就是很简单的use()API。

代码语言:javascript
复制
const Koa = require(‘koa’);
const app = new Koa();

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

app.listen(3000);

甚至实际应用必不可少的路由,对Koa来说也是一个中间件。

代码语言:javascript
复制
const Koa = require(‘koa’);
const Router = require(‘koa-router’);

const app = new Koa();
const router = new Router();

router.get(‘/‘, (ctx, next) => {
  // ctx.router available
});

app
  .use(router.routes())
  .use(router.allowedMethods());

Koa整体调用流程

原生Node实现一个Http Server很是简单:

代码语言:javascript
复制
const http = require(‘http’);

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

抽象Koa调用如下

代码语言:javascript
复制
class Koa {
    middleware = [];

    // 监听端口,开启服务
    public listen(...args) {
        const server = http.createServer(this.callback());
        return server.listen(...args);
    }

    // 收集中间件
    public use(fu) {
        this.middleware.push(fn);
        return this;
    }

    // 请求callback
    private callback() {
        const fn = compose(this.middleware)
        const handleRequest = (req, res) => {
            const ctx = this.createContext(req,res);
            return this.handleRequest(ctx, fn)
        }
    }

    private handleRequest(ctx, fnMiddleware) {
        const res = ctx.res;
        res.statusCode = 404;
        const onerror = err => ctx.onerror(err)
        const handleResponse = () => this.respond(ctx) 
        onFinished(res, onerror); // 做收尾工作,例如关闭文件,socket链接等
        return fnMiddleware(ctx).then(handleResponse).catch(onerror)
    }

    // 集中处理请求响应的收尾,减少重复业务代码
    private respond(ctx) {
        // allow bypassing koa
        if (false === ctx.respond) return;

        if (!ctx.writable) return;

        const res = ctx.res;
        let body = ctx.body;
        const code = ctx.status;

        // ignore body
        if (statuses.empty[code]) {
            // strip headers
            ctx.body = null;
            return res.end();
        }

        if ('HEAD' == ctx.method) {
            if (!res.headersSent && isJSON(body)) {
                ctx.length = Buffer.byteLength(JSON.stringify(body));
            }
            return res.end();
        }

        // status body
        if (null == body) {
            if (ctx.req.httpVersionMajor >= 2) {
                body = String(code);
            } else {
                body = ctx.message || String(code);
            }
            if (!res.headersSent) {
                ctx.type = 'text';
                ctx.length = Buffer.byteLength(body);
            }
            return res.end(body);
        }

        // responses
        if (Buffer.isBuffer(body)) return res.end(body);
        if ('string' == typeof body) return res.end(body);
        if (body instanceof Stream) return body.pipe(res);

        // body: json
        body = JSON.stringify(body);
        if (!res.headersSent) {
            ctx.length = Buffer.byteLength(body);
        }
        res.end(body);
    }

} 

是不是比想象的还要简单😄

Koa 中间件“洋葱模型”

Koa最为人称道的就是这点。甚至Koa在GitHub中的简介只是:

Expressive middleware for node.js using ES2017 async functions

下面这张图很好的表达了什么是”洋葱模型“。

Koa中间件洋葱模型
Koa中间件洋葱模型

洋葱的每一层就是中间件。这种精巧结构的实现实际上不在Koa源码中,是由koajs/compose 这个独立的库实现的,源码更加的简单。

代码语言:javascript
复制
'use strict'

/**
 * Expose compositor.
 */

module.exports = compose

/**
 * Compose `middleware` returning
 * a fully valid middleware comprised
 * of all those which are passed.
 *
 * @param {Array} middleware
 * @return {Function}
 * @api public
 */

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  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, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

需要注意的是

  • Promise.resolve()支持,同步和异步函数,因此中间件函数也都支持同步和异步函数。
  • 中间件的next()时间上就是下一个中间件函数,如果你不调用,之后的其它中间件都不会调用了。

实现上compose这个简单精巧的函数在前端界很有名了,Redux的插件系统也是取经于此。

Koa源码阅读

Koa 在众多NodeJs框架中,以短小精悍而著称,核心代码只有大约570行,非常适合源码阅读。

实际上核心来说,Koa主要是两块

  • 中间件系统
  • 对请求结构封装为更为易用的ctx对象。

本文就核心阅读中间件的源码。

Koa使用

中间件可以理解为插件,对于Koa来说,就是很简单的use()API。

代码语言:javascript
复制
const Koa = require(‘koa’);
const app = new Koa();

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

app.listen(3000);

甚至实际应用必不可少的路由,对Koa来说也是一个中间件。

代码语言:javascript
复制
const Koa = require(‘koa’);
const Router = require(‘koa-router’);

const app = new Koa();
const router = new Router();

router.get(‘/‘, (ctx, next) => {
  // ctx.router available
});

app
  .use(router.routes())
  .use(router.allowedMethods());

Koa整体调用流程

原生Node实现一个Http Server很是简单:

代码语言:javascript
复制
const http = require(‘http’);

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

抽象Koa调用如下

代码语言:javascript
复制
class Koa {
    middleware = [];

    // 监听端口,开启服务
    public listen(...args) {
        const server = http.createServer(this.callback());
        return server.listen(...args);
    }

    // 收集中间件
    public use(fu) {
        this.middleware.push(fn);
        return this;
    }

    // 请求callback
    private callback() {
        const fn = compose(this.middleware)
        const handleRequest = (req, res) => {
            const ctx = this.createContext(req,res);
            return this.handleRequest(ctx, fn)
        }
    }

    private handleRequest(ctx, fnMiddleware) {
        const res = ctx.res;
        res.statusCode = 404;
        const onerror = err => ctx.onerror(err)
        const handleResponse = () => this.respond(ctx) 
        onFinished(res, onerror); // 做收尾工作,例如关闭文件,socket链接等
        return fnMiddleware(ctx).then(handleResponse).catch(onerror)
    }

    // 集中处理请求响应的收尾,减少重复业务代码
    private respond(ctx) {
        // allow bypassing koa
        if (false === ctx.respond) return;

        if (!ctx.writable) return;

        const res = ctx.res;
        let body = ctx.body;
        const code = ctx.status;

        // ignore body
        if (statuses.empty[code]) {
            // strip headers
            ctx.body = null;
            return res.end();
        }

        if ('HEAD' == ctx.method) {
            if (!res.headersSent && isJSON(body)) {
                ctx.length = Buffer.byteLength(JSON.stringify(body));
            }
            return res.end();
        }

        // status body
        if (null == body) {
            if (ctx.req.httpVersionMajor >= 2) {
                body = String(code);
            } else {
                body = ctx.message || String(code);
            }
            if (!res.headersSent) {
                ctx.type = 'text';
                ctx.length = Buffer.byteLength(body);
            }
            return res.end(body);
        }

        // responses
        if (Buffer.isBuffer(body)) return res.end(body);
        if ('string' == typeof body) return res.end(body);
        if (body instanceof Stream) return body.pipe(res);

        // body: json
        body = JSON.stringify(body);
        if (!res.headersSent) {
            ctx.length = Buffer.byteLength(body);
        }
        res.end(body);
    }

} 

是不是比想象的还要简单😄

Koa 中间件“洋葱模型”

Koa最为人称道的就是这点。甚至Koa在GitHub中的简介只是:

Expressive middleware for node.js using ES2017 async functions

下面这张图很好的表达了什么是”洋葱模型“。

Koa中间件洋葱模型
Koa中间件洋葱模型

洋葱的每一层就是中间件。这种精巧结构的实现实际上不在Koa源码中,是由koajs/compose 这个独立的库实现的,源码更加的简单。

代码语言:javascript
复制
'use strict'

/**
 * Expose compositor.
 */

module.exports = compose

/**
 * Compose `middleware` returning
 * a fully valid middleware comprised
 * of all those which are passed.
 *
 * @param {Array} middleware
 * @return {Function}
 * @api public
 */

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  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, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

需要注意的是

  • Promise.resolve()支持,同步和异步函数,因此中间件函数也都支持同步和异步函数。
  • 中间件的next()时间上就是下一个中间件函数,如果你不调用,之后的其它中间件都不会调用了。

实现上compose这个简单精巧的函数在前端界很有名了,Redux的插件系统也是取经于此。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • toc
  • Koa源码阅读
    • Koa使用
      • Koa整体调用流程
        • Koa 中间件“洋葱模型”
        • Koa源码阅读
          • Koa使用
            • Koa整体调用流程
              • Koa 中间件“洋葱模型”
              相关产品与服务
              消息队列 TDMQ
              消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档