1. Koa 概述
Koa 是一个新的 web 框架, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有 表现力、更健壮的基石。koa是Express的下一代机遇node.js 的web框架
Koa2 完全使用Promse并配合async 来实现异步
1.1 为什么需要koa框架,他解决了什么问题
我们首先来看下原生的http模块
const http = require('http')
const fs = require('fs')
const server = http.createServer((request, response) => {
// response.end('hello ...')
const { url, method ,headers} = request
if (url === '/' && method === 'GET'){
// 静态页面服务
fs.readFile('index.html',(err,data) => {
response.statusCode = 200
response.setHeader('Content-Type','text/html')
response.end(data)
})
}else if(url === '/users' && method === 'GET'){
// Ajax服务
response.writeHead(200,{
'Content-Type': 'application/json'
})
response.end(JSON.stringify({
name : 'laowang'
}))
}else if(method === 'GET' && headers.accept.indexOf('image/*') !== -1){
// 图片文件服务
fs.createReadStream('./'+url).pipe(response)
}
})
server.listen(3000)
我们看上面原生的http 模块其实已经够强大啦,静态页面服务,Ajax 服务,图片服务都能满足,后端不就是做这些事件的呀!仔细观察会发现一以下问题
-
路由问题if else => 过多 (可以用策略模式解决,比如vue-router react-router 的实现都用了策略模式)
-
重复代码比较多 比如statusCode 赋值问题 Content-Type 设置问题
-
请求体解析与响应体包装,原始代码过于臃肿
-
请求的解析源代码太多
-
api不优雅(比如response.end()为什么是end,不是很了解node的小伙伴是不会明白的,不能名知意)
-
切面描述不方便(AOP 切面编程)比如一个转账的逻辑在用户处理前需要鉴权,在操作前后需要日志 原生的不能很好的处理这个
面向切面编程一般分为语言级,和框架级的,前端一般都是框架级别的。例如:axios vue路由首位,vue 生命周期钩子。解决方式引入洋葱圈模型
2. Koa2 的特点
- 轻量,无捆绑
- 中间价架构
- 优雅的API 设计
- 增强的错误处理
安装 npm i koa -S
中间件机制,请求,响应处理
中间件机制主要看请求响应之间
const Koa = require('koa')
const app = new Koa()
// 通过引入洋葱圈模型的方式实现AOP
app.use(async (ctx, next) => {
// ctx 上下文
const start = Date.now();
await next() // 执行下一行
const end = Date.now();
// 计算时间搓,耗时差值
console.log(`请求${ctx.url}耗时间${parseInt(end - start)}ms`)
})
app.use(async (ctx, next) => {
const expire = Date.now() + 100;
// 循环延迟一段时间在执行
while (Date.now() < expire) {
console.log("aaaa")
// 优雅的api
ctx.body = { name: 'tom' }
}
})
app.listen(3000, () => {
console.log("端口起于3000")
})
// 通过洋葱圈模型和 优雅api的提供让我将原生的node.js 进行规模的应用的可能
/*
koa2 没什么没看见引入路由?
koa2 的前身是express 是内置路由的,但是koa2 把路由拆分出去啦,因为中间件引入策略模式可以很轻松的实现路由 中间件
*/
3. Koa 原理源码分析
看下面原生的http 代码 考虑如何去封装
const http = require('http')
const server = http.createServer((req, res)=>{
res.writeHead(200)
res.end('hi mingming')
})
server.listen(3001,()=>{ console.log('监听端口3000')
})
- 考虑封装 要求看不到具体实现,而且可以设置业务逻辑 其实业务逻辑只有(req, res)=>{ res.writeHead(200) res.end('hi mingming') }
仿照koa2 的代码 去封装比如自己写个koa;就这样使用(伪代码)
const mykoa = require('./mykoa')
const app = new mykoa()
app.use((req, res) => {
res.writeHead(200)
res.end('hi mingming')
})
app.listen(3000, () => {
console.log("server at 3000")
})
封装的代码: 目标是用更简单化,流程化,模块化的方式实现回调部分
const http = require('http');
class mykoa {
listen(...args){
const server = http.createServer((req,res)=>{
this.callback(req,res)
})
// 开启端口
server.listen(...args)
}
use(callback){
this.callback = callback
}
}
module.exports = mykoa
目前为止mykoa 只是个马甲,要真正实现目标还需要引入上下文(context) 和中间件机制(middleware)
3.1 koa 上下文context
Koa 为了能够简化API引入上下文context的概念,将原始请求对象req和响应对象res 封装并且挂在到context 上,并且在context上设置ge tter和setter,从而简化操作。
使用方法接近koa
app.use(ctx=>{
ctx.body="hehe"
})
3.2 知识储备getter/settet 方法
const wkm={
msg:{
name:'wkm'
},
get name(){
return this.msg.name
},
set name(val){
this.msg.name=val
}
}
console.log(wkm.name)
// 修改 name
wkm.name="wkmhaha"
console.log(wkm.name)
console.log("aaa")
3.3 参照源码封装request, response 和context
// request js
module.exports = {
get url() {
return this.req.url;
},
get method(){
return this.req.method.toLowerCase()
}
};
// response js
module.exports = {
get body() {
return this._body;
},
set body(val) {
this._body = val;
} };
// context.js
module.exports = {
get url() {
return this.request.url;
},
get body() {
return this.response.body;
},
set body(val) {
this.response.body = val;
},
get method() {
return this.request.method
} };
// mykoa.js
const http = require('http');
let context = require('./context');
let request = require("./response");
let response = require("./response");
class mykoa {
listen(...args){
const server = http.createServer((req,res)=>{
// 创建上下文
let ctx = this.createContext(req,res)
this.callback(ctx)
// 响应
res.end(ctx.body)
})
server.listen(...args);
}
use(callback){
console.log("回调函数11111",callback);
this.callback = callback
}
// 构建上下文,把res和req 挂载在ctx 之上,并且在ctx.req 和ctx.request.req 同时保存
createContext(req,res){
const ctx = Object.create(context);
ctx.request = Object.create(request);
ctx.response = Object.create(response);
ctx.req = ctx.request.req =req;
ctx.res = ctx.response.res = res;
console.log(ctx);
return ctx
}
}
module.exports = mykoa
上面代码实现啦上下文,下面我们来开中间件的封装
4. 实现koa的中间件机制
Koa中间件机制:koa中间件机制就是函数式 组合compose的概念,将一组需要顺序执行的函数符合成一个函数,外层函数的参数实际是内层函数的返回值。洋葱圈模型可以形象表示这种极致,是源码中的精髓和难点
4.1 中间件知识储备:函数组合
const add = (x,y) =>x+y
const square = z => z*z;
const fn= (x,y)=> square(add(x,y))
console.log(fn(1,1))
上面就算是两次函数组合调用,我们可以把他合成一个函数
const add = (x,y) =>x+y
const square = z => z*z;
const compose = (fn1,fn2) =>(...args)=>fn2(fn1(...args))
const fn = compose(add,square)
console.log("susess",fn(1,1))
多个函数组合:中间件的数目是不固定的,我们可以用数组来模拟
const compose = (...[first,...other]) => (...args) => {
let ret = first(...args)
other.forEach(fn => {
ret = fn(ret)
})
return ret }
const fn = compose(add,square)
console.log(fn(1, 2))
异步的中间件:上面的函数都是同步的,挨个遍历即可,如果是异步函数,那么他就应该是个promise,我们要支持async+await 的中间件,所以我们要等待异步结束后在执行下一个中间件
function compose(middlewares) {
return function() {
return dispatch(0);
// 执行第0个
function dispatch(i) {
let fn = middlewares[i];
if (!fn) {
return Promise.resolve();
}
return Promise.resolve(
fn(function next() {
// promise完成后,再执行下一个
return dispatch(i + 1);
})
); }
}; }
async function fn1(next) {
console.log("fn1");
await next();
console.log("end fn1");
}
async function fn2(next) {
console.log("fn2");
await delay();
await next();
console.log("end fn2");
}
function fn3(next) {
console.log("fn3");
}
function delay() {
return new Promise((reslove, reject) => {
setTimeout(() => {
reslove();
}, 2000); });
}
const middlewares = [fn1, fn2, fn3];
const finalFn = compose(middlewares);
finalFn();
```js
### 4.2 函数组合应用到koa 中
```js
// mykoa.js
const http = require('http')
const context = require('./context')
const request = require('./request')
const response = require('./response')
class mykoa {
constructor(){
this.middlewares = []
}
listen(...args) {
const server = http.createServer(async (req, res) => {
// 创建上下文
const ctx = this.createContext(req, res)
const fn = this.compose(this.middlewares)
await fn(ctx)
// this.callback(req,res)
// this.callback(ctx)
res.end(ctx.body)
})
server.listen(...args)
}
// use(callback) {
// this.callback = callback
// }
use(middleware){
this.middlewares.push(middleware)
}
createContext(req, res) {
const ctx = Object.create(context)
ctx.request = Object.create(request)
ctx.response = Object.create(response)
ctx.req = ctx.request.req = req
ctx.res = ctx.response.res = res
return ctx
}
compose(middlewares) {
return function (ctx) {
return dispatch(0)
function dispatch(i) {
let fn = middlewares[i]
if (!fn) {
return Promise.resolve()
}
return Promise.resolve(
fn(ctx, function next() {
return dispatch(i + 1)
})
)
}
}
}
}
module.exports = mykoa
// index.js
const mykoa = require('./mykoa')
const app = new mykoa()
const delay = () => new Promise(resolve => setTimeout(() => resolve() ,2000));
app.use(async (ctx, next) => {
ctx.body = "1";
await next();
ctx.body += "5";
});
app.use(async (ctx, next) => {
ctx.body += "2";
await delay();
await next();
ctx.body += "4";
});
app.use(async (ctx, next) => {
ctx.body += "3";
});
app.listen(4000, () => {
console.log("server at 4000")
})
5 koa 路由策略模式
// router.js
class Router {
constructor() {
this.stack = [];
}
register(path, methods, middleware) {
let route = {path, methods, middleware}
this.stack.push(route);
}
// 现在只支持get和post,其他的同理
get(path,middleware){
this.register(path, 'get', middleware);
}
post(path,middleware){
this.register(path, 'post', middleware);
}
routes() {
let stock = this.stack;
return async function(ctx, next) {
let currentPath = ctx.url;
console.log("url",ctx.url,ctx.method)
let route;
for (let i = 0; i < stock.length; i++) {
let item = stock[i];
if (currentPath === item.path && item.methods.indexOf(ctx.method) >= 0) {
// 判断path和method
route = item.middleware;
break;
}
}
if (typeof route === 'function') {
route(ctx, next);
return;
}
await next();
};
}
}
module.exports = Router;
const mykoa = require('./mykoa');
const app = new mykoa();
const Router = require('./router')
const router = new Router()
router.get('/index', async ctx => { ctx.body = 'index page'; });
router.get('/post', async ctx => { ctx.body = 'post page'; });
router.get('/list', async ctx => { ctx.body = 'list page'; });
router.post('/index', async ctx => { ctx.body = 'post page'; });
// 路由实例输出父中间件 router.routes()
app.use(router.routes());
app.listen(3000)
以上就简单的实现个简版的koa,如果不对的的地方希望大家指点呀