download:PyTorch入门到进阶 实战计算机视觉与自然语言处理项目
源码学习思绪
Koa的中心文件一共有四个:application.js、context.js、request.js、response.js。一切的代码加起来不到 2000 行,非常笨重,而且大量代码集中在 request.js 和 response.js 关于恳求头和响应头的处置,真正的中心代码只要几百行。
另外,为了更直观地梳理 koa 的运转原理和逻辑,还是经过调试来走一遍流程,本文将分离调试源码停止剖析。
以下面代码调试为例:
const Koa = require('koa')
const app = new Koa()
const convert = require('koa-convert');
// 日志中间件
app.use(async(ctx, next) => {
console.log('middleware before await');
const start = new Date()
await next();
console.log('middleware after await');
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
app.use(async(ctx, next) => {
console.log('response');
ctx.body = "response"
})
app.listen(3000);
复制代码
node 的调试方式比拟多,可参考 Node.js 调试大法稍做理解。
一、application
application.js 是 koa 的入口文件,里面导出了 koa 的结构函数,结构函数中包含了 koa 的主要功用完成。
导出一个结构函数 Application,这个结构函数对外提供功用 API 办法,从主要 API 办法动手剖析功用完成。
1. listen
application 结构函数经过 node 中 http 模块,完成了 listen 功用:
/**
* Shorthand for:
*
* http.createServer(app.callback()).listen(...)
*
* @param {Mixed} ...
* @return {Server}
* @api public
*/
listen (...args) {
debug('listen')
const server = http.createServer(this.callback()) // 返回 http.Server 类的新实例,并运用this.callback()回调处置每个单独恳求
return server.listen(...args) // 启动 HTTP 效劳器监听衔接 完成 KOA 效劳器监听衔接
}
复制代码
2. use
use 办法将接纳到的中间件函数,全部添加到了 this.middleware ,以便后面按次第调用各个中间件,假如该办法接纳了非函数类型将会报错 'middleware must be a function!'。
/**
* Use the given middleware `fn`.
*
* Old-style middleware will be converted.
*
* @param {Function} fn
* @return {Application} self
* @api public
*/
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
// 关于 generator 类型的中间件函数,经过 koa-convert 库将其停止转换,以兼容 koa2 中的koa的递归调用。
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
}
复制代码
3. callback
上面 listen 函数在效劳启动时,createServer 函数会返回 callback 函数的执行结果。
在效劳启动时,callback函数执行将会完成中间件的兼并以及监听框架层的错误恳求等功用。
然后返回了 handleRequest 的办法,它接纳 req 和 res 两个参数,每次效劳端收到恳求时,会依据 node http 原生的 req 和 res,创立一个新的 koa 的上下文 ctx。
/**
* Return a request handler callback
* for node's native http server.
*
* @return {Function}
* @api public
*/
callback () {
const fn = compose(this.middleware) // 经过 compose 兼并中间件,后面分离koa-compose源码剖析
if (!this.listenerCount('error')) this.on('error', this.onerror)
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res)
return this.handleRequest(ctx, fn)
}
return handleRequest
}
复制代码
在 application.js 中,经过 compose 将中间件停止了兼并,是 koa 的一个中心完成。
能够看到 koa-compose 的源码,完成十分简单,只要几十行:
/**
* @param {Array} middleware 参数为 middleware 中间件函数数组, 数组中是一个个的中间件函数
* @return {Function}
* @api public
*/
function compose (middleware) { // compose函数需求传入一个函数数组队列 [fn,fn,fn...]
// 假如传入的不是数组,则抛出错误
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
// 数组队列中有一项不为函数,则抛出错误
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// 初始下标为-1
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
复制代码
compose 接纳一个中间件函数的数组,返回了一个闭包函数,闭包中维护了一个 index 去记载当前调用的中间件。
里面创立了一个 dispatch 函数,dispatch(i) 会经过 Promise.resolve() 返回 middleware 中的第 i 项函数执行结果,即第 i + 1 个 app.use() 传入的函数。 app.use() 回调的第二个参数是 next,所以当 app.use() 中的代码执行到 next() 时,便会执行 dispatch.bind(null, i + 1)),即执行下一个 app.use() 的回调。
依次类推,便将一个个 app.use() 的回调给串联了起来,直至没有下一个 next,边会按次第返回执行每个 app.use() 的 next() 后面的逻辑。最终经过 Promise.resolve() 返回第一个 app.use() 的执行结果。这里能够分离洋葱模型去了解。
4. createContext
再来看 createContext 函数,一大串的赋值骚操作,我们细细解读一下:
-
已知从 context.js、request.js、response.js 引入对象context、request和response,并依据这三个对象经过 Object.create() 生成新的context、request和response对象,避免引入的原始对象被污染;
-
经过 context.request = Object.create(this.request) 和 context.response = Object.create(this.response) 将 request 和 response 对象挂载到了 context 对象上。这局部对应了 context.js 中delegate 的拜托局部(有关 delegate 可见后面 koa 中心库局部的解读),能让 ctx 直接经过 ctx.xxx 去访问到 ctx.request.xxx 和 ctx.response.xxx;
-
经过一系列的赋值操作,将原始的 http 恳求的 res 和 req,以及 Koa 实例app 等等分别挂载到了 context、request 和 response 对象中,以便于在 context.js、request.js 和response.js 中针对原始的恳求、相应参数等做一些系列的处置访问,便于用户运用。
const response = require('./response') const context = require('./context') const request = require('./request') /**
- Initialize a new context.
- @api private */ createContext (req, res) { const context = Object.create(this.context) const request = context.request = Object.create(this.request) const response = context.response = Object.create(this.response) context.app = request.app = response.app = this context.req = request.req = response.req = req context.res = request.res = response.res = res request.ctx = response.ctx = context request.response = response response.request = request context.originalUrl = request.originalUrl = req.url context.state = {} return context } 复制代码