Koa 是轻量级的 HTTP 框架,用法非常简单,4 行代码就能写一个 hello world 接口:
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next) => ctx.body = 'hello world')
app.listen(3000, () => console.log(`server start at localhost:3000`))
接下来就实现一个简易版的 Koa。
首先 Koa 是基于 Node.js 中的 http 模块和 events 模块的,基本结构如下:
- listen 方法监听端口
- use 方法添加中间件
- 内部 middlewares 数组保存中间件函数
其核心在于 Koa 把 http 模块里面的 req 和 res 两个参数封装成了一个强大的 ctx 上下文参数,保留了原有所有 API 并扩充了自己的方法。首先要写两个对象:
- request 封装后的请求对象,扩充 createServer 的回调参数 req
- response 封装后的相应对象,扩充 createServer 的回调参数 res
在写这两个对象之前,先写两个辅助函数:
function defineGetter(obj, target, keys) { // 访问 A.x 被代理到返回 B.x
keys.forEach((key) => Object.defineProperty(obj, key, {
configurable: true,
get() {
return this[target][key]
},
}))
}
function defineSetter(obj, target, keys) { // 设置 A.x 被代理到设置 B.x
keys.forEach((key) => Object.defineProperty(obj, key, {
configurable: true,
set(value) {
this[target][key] = value
},
}))
}
接下来定义 request 对象,对于原生 req 的一些属性,例如 method、url、headers 直接保留,扩充了 query 和 path 属性
const url = require('url')
const request = {
get query() { return url.parse(this.req.url, true).query }, // 请求参数对象
get path() { return url.parse(this.req.url, true).pathname }, // 请求路径
}
const baseKeys = ['method', 'url', 'headers'] // 原生 req 对象的属性
defineGetter(request, 'req', baseKeys) // 访问 request 的属性时,代理到 req 对象
然后定义 resposne 对象,这里最核心的就是定义 body 访问器属性:
const response = {
get body() { return this._body }, // 获取返回值
set body(body) { this._body = body }, // 设置返回值
}
接下来就是 context 对象,把 request 和 response 对象封装在了一起:
const context = {}
defineGetter(context, 'request', baseKeys.concat(['query', 'path'])) // 访问 context 代理到 request
defineGetter(context, 'response', ['body']) // 访问 context 代理到 response
defineSetter(context, 'response', ['body']) // 设置 context 代理到 resposne
最后实现 Koa:
const http = require('http')
const events = require('events')
class Koa extends events.EventEmitter {
request = Object.create(request) // 每次 new Koa 创建全新 request
response = Object.create(response) // 每次 new Koa 创建全新 response
context = Object.create(context) // 每次 new Koa 创建全新上下文
middlewares = [] // 保存中间件
listen(...args) { // 监听端口
return http.createServer(this.handleRequest.bind(this)).listen(...args)
}
handleRequest(req, res) { // 处理请求
const ctx = this.createContext(req, res)
this.compose(ctx).then(() => {
const body = ctx.body
if (body) {
if (typeof body === 'object') {
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(body))
} else {
res.end(body.toString())
}
} else {
res.statusCode = 404
res.end(`Not Found`)
}
})
}
createContext(req, res) { // 每次请求创建全新上下文
const ctx = Object.create(this.context)
const request = Object.create(this.request)
const response = Object.create(this.response)
ctx.request = request
ctx.req = ctx.request.req = req
ctx.response = response
ctx.res = ctx.response.res = res
return ctx
}
use(fn) {
this.middlewares.push(fn) // 添加到中间件队列
}
compose(ctx) { // 实现洋葱模型
const dispatch = (i) => {
if (i === this.middlewares.length) return Promise.resolve()
return Promise.resolve(this.middlewares[i](ctx, () => dispatch(i + 1)))
}
return dispatch(0)
}
}
上面的代码加起来只有 80 行,涵盖了 Koa 的核心思想,可以实现 Koa 的基础功能了。实际上,Koa 的源码只有 4 个文件:
application.js定义了 Koa 类context.js定义上下文 ctx 对象request.js扩充 req 对象response.js扩充 res 对象
代码非常精简,感兴趣的可以看下。