乐趣区

关于koa.js:KoaJS

前言
koa 致力于成为一个更小、更富裕表现力、更强壮的、更轻量的 web 开发框架。因为它所有性能都通过插件实现,这种插拔式的架构设计模式,很合乎 unix 哲学。
一个简略的服务,如下:
const Koa = require(‘koa’)
let app = new Koa()
app.use((ctx, next) => {

console.log(ctx) 

})
app.listen(4000)
复制代码
而后在浏览器端关上 http://127.0.0.1:4000 即可拜访
若没有指定返回 body,koa 默认解决成了 Not Found
本文内容:

中间件原理(联合代码)

原理
中间件实现思路
了解上述洋葱模型

浏览源码

app.listen()
ctx 挂载内容

context.js
request.js
response.js
挂载 ctx

next 构建的洋葱模型

app.use((ctx, next) =< {…})
中间件含异步代码如何保障正确执行
返回报文
解决屡次调用 next 导致凌乱问题

基于事件驱动去解决异样

koa2, koa1 和 express 区别

一、中间件原理(联合代码)
原理

中间件执行就像穿梭洋葱一样,最早 use 的中间件,就放在最外层。解决程序横穿洋葱,从左到右,右边接管一个 request,左边输入返回 response;
个别的中间件都会执行两次,调用 next 之前为第一次,调用 next 时把管制传递给上游的下一个中间件。当上游不再有中间件或者没有执行 next 函数时,就将顺次复原上游中间件的行为,让上游中间件执行 next 之后的代码;

如下代码:
const Koa = require(‘koa’)
const app = new Koa()
app.use((ctx, next) => {

console.log(1)
next()
console.log(3)

})
app.use((ctx) => {

console.log(2)

})
app.listen(9001)

执行后果是 1 =>2=>3

复制代码
中间件实现思路

留神其中的 compose 函数,这个函数是实现中间件洋葱模型的要害

// 场景模仿
// 异步 promise 模仿
const delay = async () => {
return new Promise((resolve, reject) => {

setTimeout(() => {console.log('delay 2000ms')
  resolve();}, 2000);

});
}
// 两头间模仿
const fn1 = async (ctx, next) => {
console.log(1);
await next();
console.log(2);
}
const fn2 = async (ctx, next) => {
console.log(3);
await delay();
await next();
console.log(4);
}
const fn3 = async (ctx, next) => {
console.log(5);
}

const middlewares = [fn1, fn2, fn3];

// compose 实现洋葱模型
const compose = (middlewares, ctx) => {
const dispatch = (i) => {

let fn = middlewares[i];
if(!fn){return Promise.resolve() }
return Promise.resolve(fn(ctx, () => {return dispatch(i+1);
}));

}
return dispatch(0);
}

compose(middlewares, 1);
复制代码
了解上述洋葱模型
const fn1 = async (ctx, next) => {

console.log(1); 

const fn2 = async (ctx, next) => {console.log(3); 
    await delay(); 

    const fn3 = async (ctx, next) => {console.log(5); 
    }

    console.log(4); 
}

console.log(2); 

}

1 3 5 4 2

复制代码
看完这个,大略理解 koa 的中间件原理了吧。
接下来,咱们一起看下源码。
二、浏览源码

外围文件四个

application.js:简略封装 http.createServer()并整合 context.js
application.js 是 koa 的入口文件,它向外导出了创立 class 实例的构造函数,
它继承了 events,这样就会赋予框架事件监听和事件触发的能力。
application 还裸露了一些罕用的 api,比方 toJSON、listen、use 等等。

listen 的实现原理其实就是对 http.createServer 进行了一个封装,
重点是这个函数中传入的 callback,
它外面蕴含了中间件的合并,上下文的解决,对 res 的非凡解决。

use 是收集中间件,将多个中间件放入一个缓存队列中,
而后通过 koa-compose 这个插件进行递归组合调用这一些列的中间件。
复制代码

context.js:代理并整合 request.js 和 response.js
request.js:基于原生 req 封装的更好用
response.js:基于原生 res 封装的更好用

koa 是用 ES6 实现的,次要是两个外围办法 app.listen()和 app.use((ctx, next) => {…})

  1. app.listen()
    在 application.js 中实现 app.listen()
    handleRequest()

    application.js

    const http = require(‘http’)
    class Koa {
    constructor () {

     // ...

    }

     // 解决用户申请

    handleRequest (req, res) {

     // req & res nodejs native
     // ...

    }
    listen (…args) {

     let server = http.createServer(this.handleRequest.bind(this))
     server.listen(...args)

    }
    }
    module.exports = Koa
    复制代码

  2. ctx 挂载内容
    ctx = {}
    ctx.request = {}
    ctx.response = {}
    ctx.req = ctx.request.req = req
    ctx.res = ctx.response.res = res
    ctx.xxx = ctx.request.xxx
    ctx.yyy = ctx.response.yyy
    复制代码
    咱们须要以上几个对象,最终都代理到 ctx 对象上。
    创立 context.js/request.js/response.js 三个文件

2.1 request.js 内容

request.js

const url = require(‘url’)
let request = {}
module.exports = request
复制代码
在 request.js 中,应用 ES5 提供的属性拜访器实现封装

request.js

const url = require(‘url’)
let request = {
get url () {

return this.req.url // 此时的 this 为调用的对象 ctx.request

},
get path () {

let {pathname} = url.parse(this.req.url)
return pathname

},
get query () {

let {query} = url.parse(this.req.url, true)
return query

}
// … 更多待欠缺
}
module.exports = request
复制代码
以上实现了封装 request 并代理到 ctx 上

2.2 response.js 内容

response.js

let response = {}
module.exports = response
复制代码
在 response.js 中,应用 ES5 提供的属性拜访器实现封装

response.js

let response = {
set body (val) {

this._body = val

},
get body () {

return this._body // 此时的 this 为调用的对象 ctx.response

}
// … 更多待欠缺
}
module.exports = response
复制代码
以上实现了封装 response 并代理到 ctx 上

2.3 context.js 内容

context.js 初始化

let context = {}

module.exports = context
复制代码
在 context.js 中,应用__defineGetter__ / __defineSetter__实现代理,他是 Object.defineProperty()办法的变种,能够独自设置 get/set,不会笼罩设置。

context.js

let context = {}
// 定义获取器
function defineGetter (key, property) {
context.__defineGetter__ (property, function () {

return this[key][property]

})
}
// 定义设置器
function defineSetter (key, property) {
context.__defineSetter__ (property, function (val) {

this[key][property] = val

})
}

// 🌰
// 代理 request
defineGetter(‘request’, ‘path’)
defineGetter(‘request’, ‘url’)
defineGetter(‘request’, ‘query’)
// 代理 response
defineGetter(‘response’, ‘body’)
defineSetter(‘response’, ‘body’)
module.exports = context
复制代码

2.4 application.js 挂载 ctx
在 application.js 中引入下面三个文件并放到实例上
const context = require(‘./context’)
const request = require(‘./request’)
const response = require(‘./response’)
class Koa extends Emitter{
constructor () {

super()
// Object.create 切断原型链, 深拷贝配置
this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)

}
}
复制代码
而后解决用户申请并在 ctx 上代理 request / response
createContext()
# application.js

// 创立上下文
createContext (req, res) {

let ctx = this.context
// 申请
ctx.request = this.request
ctx.req = ctx.request.req = req
// 响应
ctx.response = this.response
ctx.res = ctx.response.res = res
return ctx

}

// server connection 回调
handleRequest (req, res) {

let ctx = this.createContext(req, res)
return ctx

}
复制代码

哎嘿,有个 req 还有个 request,两个一样吗?

console.log(‘native req —-‘) // node 原生的 req
console.log(ctx.req.url)
console.log(ctx.request.req.url)
console.log(‘koa request —-‘) // koa 封装了 request
console.log(ctx.url)
console.log(ctx.request.url)
复制代码

  1. next 构建的洋葱模型

接下来实现 koa 中第二个办法 app.use((ctx, next) =< {…})
app.use((ctx, next) =< {…})
use 中寄存着一个个中间件,如 cookie、session、static… 等等一堆处理函数,并且以洋葱式的模式执行。
# application.js

constructor () {

// ...
// 寄存中间件数组
this.middlewares = []

}
// 应用中间件
use (fn) {

this.middlewares.push(fn)

}
复制代码
当解决用户申请时,冀望执行所注册的一堆中间件
compose、dispatch
# application.js

// 组合中间件
compose (middlewares, ctx) {

function dispatch (index) {
  // 迭代终止条件 取完中间件
  // 而后返回胜利的 promise
  if (index === middlewares.length) return Promise.resolve()
  let middleware = middlewares[index]
  // 让第一个函数执行完,如果有异步的话,须要看看有没有 await
  // 必须返回一个 promise
  return Promise.resolve(middleware(ctx, () => dispatch(index + 1)))
}
return dispatch(0)

}

// 解决用户申请
handleRequest (req, res) {

let ctx = this.createContext(req, res)

this.compose(this.middlewares, ctx)

return ctx

}
复制代码
以上的 dispatch 迭代函数在很多中央都有使用,比方递归删除目录,也是 koa 的外围。
中间件含异步代码如何保障正确执行
返回的 promise 次要是为了解决中间件中含有异步代码的状况
返回报文
在所有中间件执行结束后,须要渲染页面。
# application.js

// 解决用户申请
handleRequest (req, res) {

let ctx = this.createContext(req, res)

res.statusCode = 404 // 默认 404 当设置 body 再做批改

let ret = this.compose(this.middlewares, ctx)

ret.then(_ => {if (!ctx.body) { // 没设置 body
    res.end(`Not Found`)
  } else if (ctx.body instanceof Stream) { // 流
    res.setHeader('Content-Type', 'text/html;charset=utf-8')
    
    ctx.body.pipe(res)
  } else if (typeof ctx.body === 'object') { // 对象
    res.setHeader('Content-Type', 'text/josn;charset=utf-8')
    
    res.end(JSON.stringify(ctx.body))
  } else { // 字符串
    res.setHeader('Content-Type', 'text/html;charset=utf-8')
    
    res.end(ctx.body)
  }
})
return ctx

}
复制代码
须要思考多种状况做兼容。

退出移动版