前言
大家好,我是作曲家种太阳,我相信最好的学习就是输出
这次我会带大家一步步从零到1封装一个Koa框架,让你掌握koa其内部运行原理,更好的使用koa框架.\
在开始之前,你得的对node中http模块和Events模块有所使用与了解
1.介绍koa
(1)Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。---Koa官网官方介绍
Koa官网 (2).洋葱模型
2.介绍Http中res,req 与Koa中context的关系
Koa是基于Http模块封装的,在Http中res,req的对象基础上,
封装了request,response两个对象,并合并成了context对象.
画一张图你就明白了
3.了解Api与Koa目录结构
(1) 读到这里,我希望你是对Koa的基本使用与api是有过了解
或者做一些demo的.
我们下面看下Koa的api基本使用.利用api反推下Koa的内部结构组成
const Koa = require('koa');
// 通过new的方式创建一个应用
const app = new Koa();
// 使用中间件
app.use((ctx)=>{
ctx.body = {a:1}
console.log(ctx.body)
})
// 开启服务
app.listen(3000,function(){
console.log(`server start 3000`);
}) ; // server.listen
// 监听错误
app.on('error',function (err) {
console.log('err',err)
})
ps:看到这里你会发现
- 可以new获取一个Koa实例对象
- Koa内部有use,listen方法
- app.on是一个事件监听机制,所以Koa使用了event事件机制模块
(2) Koa源码的目录结构:
我们根据源码源码的目录结构,创建lib下所有文件,和package.json文件
4.开始编写Koa(application.js部分)
上一步我们晓得了koa的api与目录结构,下面我们开始编写Koa
在lib/application.js中
const EventEmitter = require('events')
const http = require('http')
const context = require("./context")
const request = require('./request')
const response = require('./response')
// 实现一个koa
class Koa extends EventEmitter {
constructor() {
super();
// 创建一个新的上下文,保证应用之间不共享上下文,不会造成混乱
this.context = Object.create(context) // this.context
// 请求
this.request = Object.create(request)
// 响应
this.response = Object.create(response)
// 中间件
this.middlewares = []
}
// 存取函数
use(middleware) {
this.middlewares.push(middleware)
}
// 执行中间件,并做一些处理
compose(ctx) {
// 我需要将middlewares 中的 所有的方法拿出来,先调用第一个,第一个调用完毕后,会调用next ,再去执行第二个
let index = -1;
const dispatch = (i) => {
// 传递的小于index下标,防止同一个中间件内多次调用 next()
if (i <= index) return Promise.reject('next() called multiple times;next() 不能在一个中间件中多次调用');
// 记录index下标
index = i;
// 到末尾的next()返回空的promise
if (this.middlewares.length === i) return Promise.resolve();
return Promise.resolve(this.middlewares[i](ctx, () => dispatch(i + 1)))
}
return dispatch(0);
}
// 创建上下文
createContext(req, res) {
// 每次请求我都穿件一个全新的上下文
let ctx = Object.create(this.context) // this.context
// 请求
let request = Object.create(this.request)
// 响应
let response = Object.create(this.response)
// 合并属性,画图演示过~
ctx.request = request
ctx.response = response
ctx.req = ctx.request.req = req
ctx.res = ctx.response.res = res
return ctx
}
// 处理请求
handleRequest = (req, res) => {
// 创建合并上下文
const ctx = this.createContext(req, res)
// 设置请求头
res.statusCode = 404
// 执行完毕中间件,再继续处理请求
this.compose(ctx).then(() => {
if (typeof ctx.body === "object") {
// 设置响应头
res.setHeader("Content-Type", "application/json;charset=utf-8")
// Json转义
res.end(JSON.stringify(ctx.body))
} else if (ctx.body) {
// 发送请求体
res.end(ctx.body)
} else {
res.end("Not Found")
}
}).catch(err => {
this.emit('error', err)
})
}
//
listen(...args) {
// 开启服务
const server = http.createServer(this.handleRequest)
server.listen(...args)
}
}
module.exports = Koa
PS:主干逻辑其实并不是很难,你需要注意的几点是:
- 需要好好琢磨的是中间件是怎么存储(use函数)的和执行(compose函数)的,里面有详细的注释
- 中间件中next()的裂解 (compose函数中dispatch方法)
- createContext函数 和 constructor函数分别都使用Object.create方法创建了新的context,request,response对象.目的就是为了每个实例对象和每次请求都有不一样的上下文.
- promise是为了保证中间件调用顺序, .then()方法时为了保证先执行中间件,再继续处理请求
5.context.js编写
Koa是用的是__defineGetter__和__defineSetter__进行属性的代理的
在lib/context.js中编写
const context = {}
// 定义属性封装
function defineGetter(target, key) {
// 定义 属性取值
context.__defineGetter__(key, function () {
return this[target][key]
})
}
//
function defineSetter(target, key) {
// 定义 属性取值
context.__defineSetter__(key, function (value) {
this[target][key] = value;
})
}
// 定义属性取值代理
defineGetter("request", "query")
defineGetter("request", "path")
defineGetter("response", "body")
//设置值
defineSetter("response", "body")
module.exports = context
ps:
这里主要是做了属性的代理,取值和设置值的时候都代理到指定的对象当中去(把对象深层的属性拍平)
建议和Koa中createContext函数结合this指向一起读,就明白了
6.request.js编写
lib/request.js中
const url = require('url')
const request = {
// 属性访问器
get url() {
return this.req.url
},
get path() {
// url.parse解析成url的对象
return url.parse(this.req.url).pathname
},
get query() {
return url.parse(this.req.url).query
}
}
module.exports = request
ps:都是是属性代理
7.response.js编写
lib/response.js中
const response = {
_body: undefined,
get body() {
return this._body
},
set body(value) {
// 用户调用ctx.body 的时候 会更改状态码
this.res.statusCode = 200;
this._body = value;
}
}
module.exports = response
ps:每次给ctx.body设置值的时候,只是在response._body设置了值而已
8.测试编写结果
把第三步骤的调用koa的api代码跑起来,看下测试是通过(别忘了引入自己编写的koa文件!) 在浏览器中访问: http://localhost:3001/
到这一步,我就一步一步的带你封装好了Koa,麻雀虽小五脏俱全,有兴趣的同学可以理解完代码,直接看看Koa的源码
本文如果对你有帮助,欢迎Start,评论,收藏三连击