实现 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