hiccLoghicc log by wccHipo日志

Koa源码阅读

toc

Intro

Koa源码阅读

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); } }

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

Koa 中间件“洋葱模型”

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

Expressive middleware for node.js using ES2017 async functions

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

Koa中间件洋葱模型

洋葱的每一层就是中间件。这种精巧结构的实现实际上不在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的插件系统也是取经于此。

Koa源码阅读

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); } }

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

Koa 中间件“洋葱模型”

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

Expressive middleware for node.js using ES2017 async functions

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

Koa中间件洋葱模型

洋葱的每一层就是中间件。这种精巧结构的实现实际上不在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的插件系统也是取经于此。