- 本文 github 地点: https://github.com/HCThink/h-blog/blob/master/source/koa2/readme.md
- github 首页(star+watch,一手动态直达): https://github.com/HCThink/h-blog
- 掘金 link , 掘金 专栏
- segmentfault 主页
原创制止擅自转载
koa2
优异的下一代 web 开辟框架。
Koa 运用顺序不是 HTTP 效劳器的1对1展示。 可以将一个或多个 Koa 运用顺序装置在一起以构成具有单个HTTP效劳器的更大运用顺序。
基础运用
疾速搭建浅易 koa server 效劳
koa 搭建一个效劳照样很简朴的, 重要代码以下, 完全代码以下. 切到主目次下,
- 装置依靠:
yarn
- 实行进口:
yarn start
import Koa from 'koa';
import https from 'https';
import open from 'open';
const Log = console.log;
const App = new Koa();
App.use(async (ctx, next) => {
ctx.body = 'Hello World';
Log('mid1 start...');
await next();
Log('mid1 end...');
});
App.use(async (ctx, next) => {
debugger;
Log('mid2 start...');
await next();
Log('mid2 end...');
});
App.use((ctx, next) => {
Log('mid3...');
});
// 效劳监听: 两种体式格局。
App.listen(3000); // 语法糖
// http.createServer(app.callback()).listen(3000);
https.createServer(App.callback()).listen(3001);
open('http://localhost:3000');
// 以下为实行递次, 实际上 http 会握手,所以输出屡次
// 以下实行特征也就是洋葱圈, 实际上熟习 async、await 则不会比较不测。
// mid1 start...
// mid2 start...
// mid3...
// mid2 end...
// mid1 end...
koa2特征
- 封装并加强 node http server[request, response],简朴易容。
- 洋葱圈处置惩罚模子。
- 基于 async/await 的天真壮大的中间件机制。
- 经由历程托付使得 api 在运用上越发便利易用。
api
参考官网供应的基础 api ,不在赘述: https://koa.bootcss.com/
部份 api 完成,参考: 源码剖析
常常使用 api
- app.listen: 效劳端口监听
- app.callback: 返回适用于 http.createServer() 要领的回调函数来处置惩罚要求。你也可以运用此回调函数将 koa 运用顺序挂载到 Connect/Express 运用顺序中。
- app.use(function): 挂载中间件的重要要领。
中心对象
context
Koa Context 将 node 的 request 和 response 对象封装到单个对象中,为编写 Web 运用顺序和 API 供应了许多有效的要领。 这些操纵在 HTTP 效劳器开辟中频仍运用,它们被增加到此级别而不是更高级别的框架,这将强迫中间件从新完成此通用功用。__每一个__ 要求都将建立一个 Context,并在中间件中作为接收器援用,或许 ctx 标识符。
- ctx.res request
- ctx.req: response
- ctx.request: koa request tool
- ctx.response: koa response tool
- ctx.cookies
- ctx.request.accepts(types): type 值多是一个或多个 mime 范例的字符串,如 application/json,扩大称号如 json,或数组 [“json”, “html”, “text/plain”]。
- request.acceptsCharsets(charsets)
…
洋葱圈
运用层面
- koa 洋葱圈实行机制图解
- 洋葱圈浅易完成版
实行体式格局:
tsc onionRings.ts --lib 'es2015' --sourceMap && node onionRings.js
main code
public use(middleware: Function) {
this.middList.push(middleware);
}
// 实行器
private async deal(i: number = 0) {
debugger;
if (i >= this.middList.length) {
return false;
}
await this.middList[i](this, this.deal.bind(this, i + 1));
}
完成思绪
- use 要领注册 middleware。
- deal 模仿一个实行器: 大抵思绪就是将下一个 middleware 作为上一个 middleware 的 next 去 await,用以保证准确的实行递次和中缀。
题目
假如习惯了回调的思绪, 你会不会有这类迷惑: 洋葱圈机制于在 一个中间件中挪用另一个中间件,被调中间件实行胜利,回到当前中间件继续今后实行,如许不停挪用,中间件许多的话, 会不会构成一个很深的函数挪用栈? 从而影响机能, 同时构成「xx 地狱」? — ps(此题目源于分享时原同事 小龙 的发问。)
实际上这是个很好的题目,对函数实行机制比较相识才会发作的疑问。消除异步代码处置惩罚,我们很轻易用同步体式格局模仿出这类挪用层级。参考: 同步体式格局。 这类形式存在显著的挪用栈题目。
我可以负责任的回复: 不会的,下一个题目。 😂 😂
不会的原因在 generator 中细致引见,一两句说不清楚。实际上我以为这里是有语法门坎的。在 generator 之前,用任何体式格局处置惩罚这个题目,都显得奇异,而且难以解挪用决层级带来的机能, 调试等带来题目。
细致申明参考: generator 真.协程
源码
KOA 源码迥殊精简, 不像 Express 封装的功用那末多, git 源码: 【
https://github.com/koajs/koa】
工程
koa2 的源码工程构造非常简约,一览无余, 没有花狸狐哨的东西。
主文件
├── History.md
├── ....
├── Readme.md
├── benchmarks
├── docs // doc
│ ├── api ......
├── lib // 源码
│ ├── application.js // 进口文件,封装了context,request,response,中心的中间件处置惩罚流程。
│ ├── context.js // context.js 处置惩罚运用上下文,内里直接封装部份request.js和response.js的要领
│ ├── request.js // request.js 处置惩罚http要求
│ └── response.js // response.js 处置惩罚http相应
├── package.json
└── test // 测试模块
├── application
....
package.json
- jest 做测试
node 版本
{ "engines": { "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" } }
- 主进口:
"main": "lib/application.js"
koa 中心模块
- 封装的 http server(node)
- 中心对象 context, request、response
- 中间件机制和剥洋葱模子的完成
- 毛病捕捉和毛病处置惩罚
源码
- application.js
application.js 是 koa 的进口,继续了events , 所以框架有事宜监听和事宜触发的才能。application 还暴露了一些常常使用的api,比方toJSON、listen、use等等。 - context.js
- request.js
- response.js
特别处置惩罚
托付
- 摘自 context.js:context.js
const proto = module.exports = {
// ...
};
delegate(proto, 'response')
.method('attachment')
.method('redirect')
.method('remove')
.method('vary')
.method('set')
.method('append')
.method('flushHeaders')
.access('status')
.access('message')
.access('body')
.access('length')
.access('type')
.access('lastModified')
.access('etag')
.getter('headerSent')
.getter('writable');
delegate(proto, 'request')
.method('acceptsLanguages')
.method('acceptsEncodings')
.method('acceptsCharsets')
.method('accepts')
.method('get')
.method('is')
.access('querystring')
.access('idempotent')
.access('socket')
.access('search')
.access('method')
.access('query')
.access('path')
.access('url')
.access('accept')
.getter('origin')
.getter('href')
.getter('subdomains')
.getter('protocol')
.getter('host')
.getter('hostname')
.getter('URL')
.getter('header')
.getter('headers')
.getter('secure')
.getter('stale')
.getter('fresh')
.getter('ips')
.getter('ip');
koa 为了轻易串连中间件,供应了一个 context 对象,而且把中心的 response, request 对象挂载在上面, 然则如许每每就形成运用上写法冗余, eg: ctx.response.body
, 而且某些对象照样常常运用的,这很不轻易,所以发作了 delegates 库,用于托付操纵, 托付以后,就可以在 ctx 上直接运用部份托付属性: ctx.body
。源码剖析以下
middleware 机制
koa 中 use 用来注册中间件,实际上是将多个中间件放入一个缓存行列中 this.middleware.push(fn);
,然后经由历程koa-compose这个插件举行递归组合。
因而严厉来说 middleware 的实行构造的构造并不在 koa 源码中完成,而是在依靠库 koa-compose
中。 koa 中运用: const fn = compose(this.middleware);
完成中间件的组合。
koa-compose 中心逻辑以下, 重要思绪大抵是: 经由历程包装 middleware List 返回一个 组装好的实行器。
组装思绪是:将下一个 middleware 举行包装【实行器 + promise 化】作为上一个 middleware 的 next【dispatch.bind(null, i + 1)】。同时给中间件供应 context 对象。
return function (context, next) {
// last called middleware #
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
// 函数洋葱的末了补上一个Promise.resolve();
if (!fn) return Promise.resolve()
try {
// middleware 是 async 函数, 返回 promise 。Promise.resolve 确保中间件实行完成
// 供应 ctx, next fn: dispatch.bind(null, i + 1)
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
koa-compose
koa-compose 是一个非常精简的库,不做零丁剖析了, 他供应了一种主调型的递归: fn(context, dispatch.bind(null, i + 1))
, 这类体式格局可以以为是’懒递归’, 将递归的实行交给主调者掌握,如许可以在更适宜的机遇实行后续处置惩罚, 但假如某个中间件不挪用 next,那末厥后的中间件就不被实行了。这和 js 协程【generator】有机制上的相似,都是运用者来掌握 next 的实行机遇, 可类比进修。
易用性处置惩罚
koa 非常易用, 原因是 koa 在源码层面做了大批的 托付 和针对庞杂对象的封装,如 request, response 的 get/set. 用以进步东西的可费用,易费用。实际上我以为这一点是当代框架非常重要的东西,离开用户的库都不是好库。koa 是好库。
- delegates 上面说过, 参考: delegates。
- get/set
request, response 两个文件千行代码, 80% 摆布的都是 get、set,参考:
另一方面,表如今 application.js 的 createContext
要领中,经由历程挂载援用和托付合营 get set 的实践合营提拔易费用,零丁不太好讲,剖析解释在源码中。
非常捕捉
- 中间件非常捕捉, koa1 中间件基于 generator + co, koa2 中间件基于 async/await, async 函数返回 promise, 所以只要在组合中间件后 catch 即可捕捉中间件非常
fnMiddleware(ctx).then(handleResponse).catch(onerror);
- 框架层发作毛病的捕捉机制, 这个经由历程继续 event 模块很轻易完成监听。
this.on('error', this.onerror);
注册的 error 事宜, 在 context.onerror 中被 emit
this.app.emit('error', err, this);
- http 非常处置惩罚 : Execute a callback when a HTTP request closes, finishes, or errors.
onFinished(res, onerror); // application.handleRequest
中间件交互
初用中间件能够会有一个疑问: 中间件怎样通讯?
事实上这是个设想弃取逻辑, 中间件之间的数据交互并非麻烦事, 迥殊是在 ECMAScript 推出 async await 以后,但题目是如许做的意义不大,原因是一切的中间件是可任意插拔组合的,这类不确定性,致使了中间件之间的数据交互就变得不稳定,最起码的数据格式就没办法牢固,就更别谈处置惩罚了。天真的插件机制致使中间件之间的交互难有一致层面的完成。
另一方面从中间件的定位来看,其之间也没必要交互,中间件不能离开 http 的要求相应而自力存在,他是效劳于全部历程的,也因而一切的中间件第一个参数就是 ctx, 这个对象挂载了 request 和 response, 以及 koa 供应的封装和东西操纵。
中心点
中缀
这是洋葱圈非常中心的支撑点, 我们轻微注意就可以发明 koa 中间件实行机制于一般 js 的实行递次很不一致, 我们看以下代码:
app.use(async (cxt, next) => {
Log(1);
await next();
Log(2);
});
app.use(async (cxt, next) => {
Log(3);
await next();
Log(4);
});
上述代码实行递次也就是洋葱圈: Log(1) -> await next (Log(3)) -> await next -> Log(4) -> Log(2).
为了保证代码根据洋葱模子的实行递次实行,顺序需要在挪用 next 的时刻让代码守候,我称之为中缀。
实际上之前想要完成这类实行递次,只能依靠 cb, promise.then 来模仿,而且即使完成了,在写法上也显得痴肥和别扭,要么是写出很胖的函数,要么是写出很长的函数。而且没法处置惩罚挪用栈的题目。
async/await 可以比较文雅的完成这类具有同步实行特征的前端代码来处置惩罚异步,代码实行到 await 这里,守候 await 表达式的实行,实行完成以后,接着今后实行。
实际上这很相似于 generator 的 yield,特征。async 也就是 generator + 实行器的一个语法糖, 参考:
async ? no , it’s generator
koa.use 得确直接运用 async 函数处置惩罚中间件及个中能够存在的异步, 而 async/await 完成上是基于 generator 。async 在运用上可讲的点一般在他的 task 放在哪,以及实行机遇 和 timeout ,promise 的实行递次等。真正的中缀特征得益于 generator。
一名不肯透漏姓名的同事问了我一个题目,怎样证实 async 是 generator + 实行器 的语法糖?这是不能不议论一个题目。相干的议论参考: Async / Await > #generator 部份讨论
生态
koa 中间件并没有一个一致的 market 之类的处所,说实话找起来不是那末轻易。假如你想找中间件的话,可以在 npm 上用 koa-
做关键字检索: https://www.npmjs.com/search?…
源码运用的中间件
- koa-compose
上面已有剖析 function isJSON(body) { if (!body) return false; if ('string' == typeof body) return false; if ('function' == typeof body.pipe) return false; if (Buffer.isBuffer(body)) return false; return true; }
- koa-convert
用于兼容处置惩罚 generator 中间件,基础可以以为是 co + generator 中间件【也依靠 koa-compose 举行构造】
other koa
社区常常使用中间件合集: some middleware