中间件的执行流程,能够用下面这张图片来生动的说明(图片使用了 Koa 2 的 async 语法):javascript
对于 Koa 1 来讲也相似,只是 async 函数换做 generator 函数,await 换做 yield 关键字。css
对于前端程序员,能够把 yield 以前的代码认为是捕获阶段,yield 以后的认为的冒泡阶段,从而理解多个中间件之间代码的执行流程。html
路由通常属于业务代码,咱们通常放在其余基础中间件以后来注册。路由的基本原理就是判断 url path, 而后决定是否执行某个中间件逻辑。前端
简单实现能够相似这样:java
const Koa = require('koa') const app = new Koa() app.use(function *(next) { if (this.path === '/home') { this.body = '首页' } else { yield next } console.log('这里会执行哦') }) app.use(function *(next) { if (this.path === '/admin') { this.body = '管理端' } }) app.listen(3000)
能够看到,对于不符合本中间件的请求 path, 就直接丢弃,并去执行下一个中间件。若是全部中间件都匹配不到,会返回 404(Koa 默认行为).node
上面代码有一个问题,就是 "console.log" 会一直执行,要解决这个也很简单。由于对于路由中间件来讲,全部逻辑都是匹配path的if判断内部的,因此对于这个不匹配的else代码,能够直接当作该 generator 的结束。能够在 yield 前面加 return 或这样修改:git
app.use(function* (next) { if (this.path !== '/') return yield next; this.body = 'we are at home!'; })
为了应对更复杂的路由功能,咱们须要引入第三方的 koa-router 路由模块。不过 Koa1 须要使用 4.x 版本的。程序员
Issue: You are using koa@1.x but koa-views@5.x needs koa@2 or above. If you are still at v1 please consider using koa-views@4.x. Note however, there are no updates supporting v1
npm i koa-router@4 -d
koa-router 暴露一个 Router 类,像 Vue.js 同样,只需建立一个 router 实例,就能够注册对应的路由规则。github
var app = require('koa')(); var Router = require('koa-router'); var myRouter = new Router(); myRouter.get('/', function *(next) { this.response.body = 'Hello World!'; }); app.use(myRouter.routes()); app.listen(3000);
从用法中显然能看出来,routers 方法返回的应该就是一个 generator 中间件函数,只是内部由 koa-router 进行了路由规则的处理和逻辑执行。开发者只需关注如何向 koa-router 对象上注册 处理中间件
。npm
koa-router 像多数路由同样支持不少 http 方法和匹配规则:
router.get() router.post() router.put() router.del() router.patch()
Koa 有不少的中间件 好比 [koa-view](
https://github.com/queckezz/k...
该中间件支持多种模板引擎。
cookie的获取和设置是 Koa 内置的context集成的能力,不须要中间件的参与。
this.cookies.get('cookieName') this.cookies
为了兼容 Koa1,咱们须要安装一个老一点的koa-router
npm i koa-router@5.x
用于打印请求日志和耗时
npm i koa-logger@1 // 1.x 支持 Koa1
// 为了兼容 Koa1 npm i koa-session@3.x
该版本的 koa-session 只须要执行其导出函数并传入一个 app 对象便可使用
const session = require('koa-session') app.use(session(app)) app.use(function * (next) { console.log(this.session.xxx) })
默认的Koa应用,咱们观察下浏览器的 Response 响应的话,会发现虽然请求时浏览器携带了 Accept-Encoding: gzip
, 但实际上响应里面并无 Content-Encoding: gzip
, 也就是说并无压缩。
安装 koa-compress@1.x
以后,就可让 Koa 默认开启对响应内容的压缩了:
app.use(require('koa-compress')())
大一点的文件才有效果哦,过小的话还比不上 'Content-Encoding' 头所占的字节的话,就有点得不偿失了。
# 为了兼容 Koa1 请安装 2.x 版本 npm i koa-csrf@2.x
该模块的导出对象是一个函数,函数会建立一个中间件,你须要将他注册到 Koa 的app里面。使用方式以下:
app.use(session(app)) // koa-csrf 的机制要依赖session能力 app.use(csrf()) // 这是koa1的用法
解决跨站请求伪造攻击,须要在客户端请求时携带一个秘密的token,这个token要确保只有服务器端知道,并且用后即焚. 其思路是,一个用户在访问页面时,服务端先把这个 csrf-token 放置到页面中,而后页面再次发起 POST 请求时,页面须要带上这个token,由服务端来校验是否是服务器颁发的token.
回到koa-csrf这个模块,在每次请求周期中, koa-csrf 都会在它的中间件内生成一个秘钥secret, 而后基于secret生成一个 csrf-token; 并把这个 csrf-token 挂在 ctx 上,把 secret 挂在session上(由于secret做为一个秘钥基于session能够针对一个独立用户,不必每次都变). 咱们把koa-csrf 中生成token过程的源码捡出来以下:
// 建立一个秘钥并放在session里,每次生成和校验csrf时都用这个token var secret = this.session.secret || (this.session.secret = tokens.secretSync()) // 摘录tokens.secretSync的实现以下: Tokens.prototype.secretSync = function secretSync () { return uid.sync(this.secretLength) // 其实就是使用 uid-safe 模块生成一个固定长度的随机uniqueId }
有了secret秘钥了,再来看下 csrf-token 咋生成的:
// 基于上一步的秘钥secret来生成csrf-token 放在ctx对象上 this._csrf = tokens.create(secret) // csrf-token的生成过程以下: Tokens.prototype.create = function create (secret) { if (!secret || typeof secret !== 'string') { throw new TypeError('argument secret is required') } // 重点在这里。其中rndm模块仅仅就是用来生成n位数的随机字符串;而_tokenize函数就是用来生成csrf-token的,其实现我摘录在下面 return this._tokenize(secret, rndm(this.saltLength)) } // tokenize实现 Tokens.prototype._tokenize = function tokenize (secret, salt) { // csrf-token 的格式为: salt随机字符串 + hash(salt+secret) return salt + '-' + hash(salt + '-' + secret) }
至此,csrf-token就生成了。接下来,你须要在 GET 请求的页面上,把 this.csrf 渲染到页面中。而后前端再次请求后端的 POST 接口时,须要带上那个token。这样,POST请求到达服务器时 koa-csrf 中间件就会在请求到来时优先进行校验。
校验规则已经显而易见了:
横线
前面的字符串做为 salt随机串
,取后面的做为 待校验的哈希[fehash]
secret
var result = hash('前端传来的salt' + '服务端秘钥secret')
那么,会不会存在黑客在中间网络窃取到某次请求的token后,再利用这个token来实施 CSRF 呢? 这个其实是没法避免的,既然黑客能窃取到http报文(说明请求被中间人劫持或站点被XSS注入),那黑客彻底能够窃取到 cookie 等信息,至关于彻底模拟了用户,这种状况下任何防范都没有做用了;只能说若是发现IP变了那就要求用户从新登陆且切换 secret。
几乎全部的网络应用所需的功能都有中间件提供。能够在官方 wiki 中看到中间件列表
对于编写公共中间件的场景来讲,更多的须要用户能自定义中间件中一些配置。此时须要支持用户对中间件进行配置。要实现可配置的中间件也简单,只须要写一个包装函数,返回一个 generator 的函数便可。例如咱们的日志中间件,能够容许用户自定义日志格式,则能够这样:
// 可自定义日志格式的中间件 const mylogger = function (format) { format = format || '{{method}} {{url}} - {{time}}' return function *(next) { const start = Date.now() yield next const ms = Date.now() - start console.log(format .replace('{{method}}', this.method) .replace('{{url}}', this.url) .replace('{{time}}', ms) ) } } // 使用该中间件 app.use(mylogger('{{time}} - {{method}} : {{url}}'))
有时可能须要将多个中间件合并为一个。对于 Generator来讲,可使用 .call(this. next) 的方式将他们合并。
const Koa = require('koa') const app = new Koa() function *a(next) { console.log('come a') yield next; console.log('end a') } function *b(next) { console.log('come b') yield next console.log('end b') } function *all(next) { console.log('come all') yield a.call(this, b.call(this, next)); console.log('end all') } app.use(all) app.listen(3000)
执行上述代码,控制台会输出:
come all come a come b end b end a end all
你必定比较疑惑为何多个 generator 函数经过 call(this, next)
是怎么作到如此合并执行的? 其实本质上 Koa 的运做也是基于合并 middlware 来执行的。这里大概是这样的:
function *a(next) { console.log('come a') yield (b中间件 的 generator 对象); console.log('end a') }
上述过程相似于 koa-compse 模块的合并能力, 这里贴一个 compose 模块的实现(引用自阮一峰的 Koa 教程):
function compose(middleware){ return function *(next){ if (!next) next = noop(); var i = middleware.length; while (i--) { next = middleware[i].call(this, next); } yield *next; } } function *noop(){}
以上就是中间件合并的原理了,合并后会返回一个新的 generator 函数。而 Koa 是如何使用 co
库把合并后的 generator 中间件函数运行起来的呢? 这个就有点复杂了,更详细的 middleware合并 和 Koa 原理能够参考: qianlongo github
本章节介绍了几个经常使用中间件,如koa-router和 koa-view,并对中间件的合并和传参进行了简单介绍。基本上 Koa 的全部使用方式都已经介绍完毕,后面就是赤裸裸的实践了