最近读了 koa2 的源码,理清楚了架构设计与用到的第三方库。本系列将分为 3 篇,分别介绍 koa 的架构设计和 3 个核心库,最终会手动实现一个简易的 koa。这是系列第 3 篇,模拟实现玩具版 koa。
源码和测试代码放在了:dongyuanxin/simple-koa
设计思想和第三方库原理都在前 2 篇详细说明了。这篇主要目的是做一个验证检验,在语法使用 ES6/7 的语法。
在开始前,安装一下需要用到的库:
npm install --save koa-compose koa-convert is-generator-function
为了说明效果,先按照正常使用 koa 的逻辑编写了测试文件。当启动它的时候,它的预期行为是:
localhost:3000
,屏幕打印hello
代码如下:
const Koa = require("./lib/application");const server = new Koa();async function middleware1(ctx, next) {console.log("1 inner");await next();console.log("1 outer");}async function middleware2(ctx, next) {ctx.res.body = "hello";console.log("2 inner");await next();console.log("2 outer");}server.use(middleware1);server.use(middleware2);server.listen(3000);
只准备了一个文件,跑通上面的逻辑即可。文件是 lib/application.js
。
首先对外暴露的就是一个继承 Emitter 的 Application 类。整体框架如下:
const http = require("http");const Emitter = require("events");const compose = require("koa-compose");module.exports = class Application extends Emitter {constructor() {super();this.middleware = []; // 中间件this.context = {}; // 上下文this.request = {}; // 请求信息this.response = {}; // 返回信息}listen(...args) {}use(fn) {}callback() {}handleRequest(ctx, fnMiddleware) {}createContext(req, res) {}onerror(error) {console.log(`error occurs: ${error.message}`);}};
继承 Emitter 事件类,是为了方便监听和处理报错。
将外面传入的中间件保存起来:
use (fn) {this.middleware.push(fn)return this}
主要用于创建上下文。外面可以通过访问 ctx 上的 req/res 拿到请求或者返回信息。
createContext (req, res) {const context = Object.create(this.context)context.request = Object.create(this.request)context.response = Object.create(this.response)context.req = reqcontext.res = rescontext.app = thiscontext.state = {}return context}
监听端口,启动服务器:
listen (...args) {const server = http.createServer(this.callback())return server.listen(...args)},callback () {const fn = compose(this.middleware)this.on('error', this.onerror)return (req, res) => {const ctx = this.createContext(req, res)return this.handleRequest(ctx, fn)}}
在 callback
方法中真是返回的内容,它的作用就是:处理请求,并且返回给客户端。
handleRequest(ctx, fnMiddleware) {const res = ctx.res// res.statusCode = 404const handleResponse = () => {res.end(res.body)}return fnMiddleware(ctx).then(handleResponse).catch(this.onerror)}
启动 index.js 后,在浏览器访问本地 3000 端口:
回到控制台,查看中间件的输出顺序是否正确: