实现koa有这几个主要步骤:
- 封装httpServer
- 上下文context和
request
、response
对象 - 中间件函数 middleware
- 错误处理
koa 核心源码里面包括这几个文件:
|-- koa
|-- lib
|-- application.js
|-- context.js
|-- request.js
|-- response.js
入口文件 application
application.js
是核心入口文件,包括导出Koa类函数和核心代码的实现:
const http = require("http")
class Koa {
constructor() {
// 上下文 context
this.ctx = Object.create({});
// 中间件函数
this.middleware = null
}
use(fn) {
// 将用户传入的函数绑定到中间件函数中
this.middleware = fn
}
handleRequest(req, res) {
let ctx = this.ctx;
// 给上下文添加 request 和 response 对象
ctx.req = req;
ctx.res = res;
// 执行中间件函数
this.middleware(ctx);
// 返回结果
ctx.body ? res.end(ctx.body) : res.end("Not Found")
}
listen() {
let server = http.createServer(this.handleRequest.bind(this))
server.listen(...arguments)
}
}
module.exports = Koa;
上面的代码已经完成了httpServer服务的封装,有最基础的context
上下文对象,绑定了request
和response
属性,可以在根目录下创建index.js
文件测试:
let Koa = require("./koa/lib/application");
let app = new Koa();
app.use((ctx) => (ctx.body = ctx.req.url))
app.listen(3000);
context、request和response
源码里面使用getter
和setter
属性,封装上下文context
的req
和res
,也就是request
和response
对象。
request
创建request.js
,加入下面代码:
let url = require('url');
module.exports = {
get path() {
return url.parse(this.req.url,true).pathname
},
get query() {
return url.parse(this.req.url,true).query
}
}
response
创建response.js
,加入下面代码:
module.exports = {
_body: "",
get body() {
return this._body
},
set body(value) {
this.res.statusCode = 200
this._body = value
},
}
context
创建context.js
,加入下面代码:
let ctx = {}
function defineGetter(property,key){
// 相当于去 property 上取值
ctx.__defineGetter__(key,function(){
return this[property][key]
});
}
function defineSetter(property,key){
// 相当于给 property 赋值
ctx.__defineSetter__(key, function (value) {
this[property][key]=value
})
}
defineGetter("request","path");
defineGetter("request","query");
defineGetter("response","body");
defineSetter("response", "body")
module.exports = ctx;
源码里面通过__defineSetter__
和__defineGetter__
将request
和response
的属性挂载到了上下文context
,接下啦修改application.js
,引入context
、response
、request
:
const context = require("./context")
const request = require("./request")
const response = require("./response")
...
constructor() {
...
// Object.create防止用户直接修改对象,保证每次new Koa都是新的对象
this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)
}
createContext(req, res) {
let ctx = this.context
/*
* ctx.request.req\ctx.req\ctx.req
* ctx.response.res\ctx.res\ctx.res
*/
ctx.request = this.request
ctx.response = this.response
ctx.request.req = ctx.req = req
ctx.response.res = ctx.res = res
return ctx
}
handleRequest(req, res) {
// 获取新的上下文
let ctx = this.createContext(req, res)
// 执行中间件函数
this.middleware(ctx);
...
}
中间件 middleware
上面的middleware
函数只绑定了一个方法,我们知道koa里面是可以绑定多个中间件函数,并且中间件函数包含上下文context
和是否继续执行的next
函数,因为koa2中使用的是async/await
的方式,所以中间件函数返回的都会是一个Promise
。
将middleware
改成数组middlewares
:
constructor() {
...
this.middlewares = []
...
}
use(fn){
//先将函数保存到中间件数组中
this.middlewares.push(fn)
}
...
接下来创建compose
方法处理中间件函数:
compose(ctx, middlewares) {
// 当前函数执行指针
let exectIndex = -1
let dispatch = async function (index) {
// 防止同一个中间件函数出现两个dispatch函数抛出异常
if (exectIndex >= index) return Promise.reject("mulit called next();")
exectIndex = index
// 全部执行完成返回Promise
if (index === middlewares.length) return Promise.resolve()
// 取出中间件函数处理
let middleware = middlewares[index]
// next函数继续取出下一个中间件函数执行
let next = () => dispatch(++index);
// 返回执行的中间件函数
return middleware(ctx, next)
}
return dispatch(0)
}
修改handleRequest
函数如下:
handleRequest(req, res) {
let ctx = this.createContext(req, res)
res.statusCode = 404
let p = this.compose(ctx, this.middlewares)
p.then(() => {
ctx.body ? res.end(ctx.body) : res.end("Not Found")
})
}
错误处理
koa 里面可以通过订阅error
事件捕获中间件函数运行过程中出现的异常:
app.on("error",(error,ctx)=>{
ctx.res.end(error.toString())
})
这理可以通过继承events
对象,获得发布订阅的能力:
const EventEmiter = require("events")
class Koa extends EventEmiter {
constructor() {
super()
...
}
...
handleRequest(req,res){
...
p.then(() => {
ctx.body ? res.end(ctx.body) : res.end("Not Found")
}).catch(error=>{
// 铺货错误后发送给error
this.emit("error", error, ctx)
})
}
}
完整的application.js
代码:
const http = require("http")
const Stream = require("stream")
const EventEmiter = require("events")
const context = require("./context")
const request = require("./request")
const response = require("./response")
class Koa extends EventEmiter {
constructor() {
super()
this.middlewares = []
// Object.create防止用户直接修改对象,保证每次new Koa都是新的对象
this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)
}
use(fn) {
this.middlewares.push(fn)
}
compose(ctx, middlewares) {
let exectIndex = -1
let dispatch = async function (index) {
if (exectIndex >= index) return Promise.reject("mulit called next();")
exectIndex = index
if (index === middlewares.length) return Promise.resolve()
let middleware = middlewares[index]
return middleware(ctx, () => dispatch(++index))
}
return dispatch(0)
}
createContext(req, res) {
let ctx = this.context
/*
* ctx.request.req\ctx.req\ctx.req
* ctx.response.res\ctx.res\ctx.res
*/
ctx.request = this.request
ctx.response = this.response
ctx.request.req = ctx.req = req
ctx.response.res = ctx.res = res
return ctx
}
handleRequest(req, res) {
let ctx = this.createContext(req, res)
res.statusCode = 404
let p = this.compose(ctx, this.middlewares)
p.then(() => {
if (ctx.body instanceof Stream) {
res.setHeader("Content-Type", "application/octet-stream")
res.setHeader("Content-Disposition", `attachment;filename=download`)
return ctx.body.pipe(res)
}
if (ctx.body) {
res.end(ctx.body)
} else {
res.end("Not Found")
}
}).catch((error) => {
this.emit("error", error, ctx)
})
}
listen() {
let server = http.createServer(this.handleRequest.bind(this))
server.listen(...arguments)
}
}
module.exports = Koa
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。