最近读了 koa2 的源码,理清楚了架构设计与用到的第三方库。本系列将分为 3 篇,分别介绍 koa 的架构设计和 3 个核心库,最终会手动实现一个简易的 koa。这是系列第 3 篇,模拟实现玩具版 koa。
源码和测试代码放在了:simple-koa
准备
设计思想和第三方库原理都在前 2 篇详细说明了。这篇主要目的是做一个验证检验,在语法使用 ES6/7 的语法。
在开始前,安装一下需要用到的库:
1 2
| npm install --save koa-compose koa-convert is-generator-function npm install --save events
|
测试文件
为了说明效果,先按照正常使用 koa 的逻辑编写了测试文件。当启动它的时候,它的预期行为是:
- 监听 3000 端口
- 加载中间件
- 浏览器访问
localhost:3000
,屏幕打印hello
- 服务器的控制台依次输出:1inner => 2innter => 2outer => 1outer
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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)
|
玩具 koa
只准备了一个文件,跑通上面的逻辑即可。文件是 lib/application.js
。
文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| 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) { const server = http.createServer(this.callback()) return server.listen(...args) }
use(fn) { this.middleware.push(fn) return this }
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) } }
handleRequest(ctx, fnMiddleware) { const res = ctx.res const handleResponse = () => { res.end(res.body) }
return fnMiddleware(ctx).then(handleResponse).catch(this.onerror) }
createContext(req, res) { const context = Object.create(this.context) context.request = Object.create(this.request) context.response = Object.create(this.response) context.req = req context.res = res
context.app = this context.state = {}
return context }
onerror(error) { console.log(`error occurs: ${error.message}`) } }
|
下面开始解析lib/application.js
文件代码
构造函数
首先对外暴露的就是一个继承 Emitter 的 Application 类。整体框架如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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
将外面传入的中间件保存起来:
1 2 3 4
| use (fn) { this.middleware.push(fn) return this }
|
createContext
主要用于创建上下文。外面可以通过访问 ctx 上的 req/res 拿到请求或者返回信息。
1 2 3 4 5 6 7 8 9 10 11 12
| createContext (req, res) { const context = Object.create(this.context) context.request = Object.create(this.request) context.response = Object.create(this.response) context.req = req context.res = res
context.app = this context.state = {}
return context }
|
listen 和 callback
监听端口,启动服务器:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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) } }
|
handleRequest
在 callback
方法中真是返回的内容,它的作用就是:处理请求,并且返回给客户端。
1 2 3 4 5 6 7 8 9 10 11
| handleRequest(ctx, fnMiddleware) { const res = ctx.res const handleResponse = () => { res.end(res.body) }
return fnMiddleware(ctx) .then(handleResponse) .catch(this.onerror) }
|
效果截图
启动 index.js 后,在浏览器访问本地 3000 端口:
回到控制台,查看中间件的输出顺序是否正确:
