nodejs篇-实现一个koa

微信扫一扫,分享到朋友圈

nodejs篇-实现一个koa

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

微信扫一扫,分享到朋友圈

nodejs篇-实现一个koa

IDC:2020年第三季度中国智能手机市场出货量约8480万台 同比下滑14.3%

上一篇

不使用VBA 日本猛人用Excel再现《勇者斗恶龙3》

下一篇

你也可能喜欢

nodejs篇-实现一个koa

长按储存图像,分享给朋友