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