

Koa源码阅读
source link: https://www.tuicool.com/articles/fiQBZbB
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 在众多NodeJs框架中,以短小精悍而著称,核心代码只有大约570行,非常适合源码阅读。
实际上核心来说,Koa主要是两块
ctx
本文就核心阅读中间件的源码。
Koa使用
中间件可以理解为插件,对于Koa来说,就是很简单的 use()
API。
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来说也是一个中间件。
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很是简单:
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调用如下
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); } }
是不是比想象的还要简单:smile:
Koa 中间件“洋葱模型”
Koa最为人称道的就是这点。甚至Koa在 GitHub 中的简介只是:
Expressive middleware for node.js using ES2017 async functions
下面这张图很好的表达了什么是”洋葱模型“。

洋葱的每一层就是中间件。这种精巧结构的实现实际上不在Koa源码中,是由 koajs/compose 这个独立的库实现的,源码更加的简单。
'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的插件系统也是取经于此。
Recommend
-
62
-
43
从今天开始阅读学习一下 Koa 源码, Koa 对前端来说肯定不陌生,使用 node 做后台大部分会选择 Koa 来做, Koa 源码的代码量其实很少,接下来让我们一层层剥离,分析...
-
21
用 Node.js 写一个 web服务器 ,我前面已经写过两篇文章了: 第一篇是不使用任何框架也能搭建一个 web服务器 ,主要是熟悉 Node.js 原生API的使用:
-
12
koa-router源码解读腾讯 高级前端工程师个人订阅号,知乎和微信同步推文,希望大家关注一波!微信订阅号:小前端看世界,id:fe_watch_world
-
11
Node学习笔记 - Koa源码阅读腾讯 高级前端工程师个人订阅号,知乎和微信同步推文,希望大家关注一波!微信订阅号:小前端看世界,id:fe_watch_world
-
13
-
12
深入源码:手写一个koa夏日Enjoy what you are doing!前言:了解koa
-
11
koa是一个非常轻量的web框架,里面除了ctx和middleware之外什么都没有,甚至连最基本的router功能都需要通过安装其他中间件来实现。不过虽然简单,但是它却非常强大,仅仅依靠中间件机制就可以构建完整的web服务。而koa的源码同样很简洁,基础代码只有不到2000...
-
10
koa router实现原理 本文两个目的 1. 了解path-to-regexp使用 2. koa-router源码解析 path-to-regexp path-to-regexp用法简介。 如何使用...
-
10
源码在这 后续应该会把中间件的也给加上去。 Comments 发表评论 取消回复电...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK