koa2中间件开发

koa2 中间件机制

Koa 是一个简单、轻量的 Web 框架。Koa 的最大特色,也是最重要的一个设计,就是中间件(middleware) 。Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。Koa中使用 app.use() 用来加载中间件,基本上Koa 所有的功能都是通过中间件实现的。每个中间件默认接受两个参数,第一个参数是 Context 对象,第二个参数是next函数。只要调用next函数,就可以把执行权转交给下一个中间件。

下图为经典的Koa洋葱模型

koa2洋葱模型

看看官网的经典示例:

  1. const Koa = require('koa')
  2. const app = new Koa()
  3. // x-response-time
  4. app.use(async (ctx, next) => {
  5. const start = Date.now()
  6. await next()
  7. const ms = Date.now() - start
  8. ctx.set('X-Response-Time', `${ms}ms`)
  9. })
  10. // logger
  11. app.use(async (ctx, next) => {
  12. const start = Date.now()
  13. await next()
  14. const ms = Date.now() - start
  15. console.log(`${ctx.method} ${ctx.url} - ${ms}`)
  16. })
  17. // response
  18. app.use(async ctx => {
  19. ctx.body = 'Hello World'
  20. })
  21. app.listen(3000)

上面的执行顺序就是:请求 ==> response-time中间件 ==> logger中间件 ==> 响应中间件 ==> logger中间件 ==> response-time中间件 ==> 响应。

请求进来,先进到x-response-time中间件,执行const start = new Date()然后遇到await next(),则暂停x-response-time中间件的执行,跳转进logger中间件,同理,最后进入响应中间件,响应中间件中没有await next()代码,则开始逆序执行,也就是再先是回到logger中间件,执行await next()之后的代码,执行完后再回到x-response-time中间件执行await next()之后的代码。

koa2 中间件编写

我们来看看如何编写中间件,其实上面的logger、x-response-time都是中间件,通过app.use注册,同时为该函数传入 ctxnext 两个参数。

ctx 作为上下文使用,包含了基本的 ctx.requestctx.response,还对 Koa 内部对一些常用的属性或者方法做了代理操作,使得我们可以直接通过 ctx 获取。比如,ctx.request.url 可以写成 ctx.url

next 参数的作用是将处理的控制权转交给下一个中间件,而 next() 后面的代码,将会在下一个中间件及后面的中间件运行结束后再执行。

  1. // middleware/logger.js
  2. module.exports = function () {
  3. return async function ( ctx, next ) {
  4. console.log( ctx.method, ctx.header.host + ctx.url )
  5. await next()
  6. }
  7. }

消息闪现中间件

前面我们实现了用户登录注册,但是没有一个友好的提示如:注册成功、登陆成功等。一般一个操作完成后,我们都希望在页面上闪出一个消息,告诉用户操作的结果。其原先是出自 rails 的,用于在页面上显示一些提示信息。

我们就来实现一个基于session的消息闪现。新建middlewares目录,并建一个flash.js

  1. module.exports = function flash (opts) {
  2. let key = 'flash'
  3. return async (ctx, next) => {
  4. if (ctx.session === undefined) throw new Error('ctx.flash requires sessions')
  5. let data = ctx.session[key]
  6. ctx.session[key] = null
  7. Object.defineProperty(ctx, 'flash', {
  8. enumerable: true,
  9. get: () => data,
  10. set: (val) => {
  11. ctx.session[key] = val
  12. }
  13. })
  14. await next()
  15. }
  16. }

这个flash消息就是将消息挂到session上再清空,只显示一次,刷新后就没有了。这个中间件可优化的地方还很多,这儿重点不是优化功能就先跳过。

我们还需添加一个显示提示的视图模板,就叫他notification.html

  1. // components/notification.html
  2. {% if ctx.flash %}
  3. <div class="notifications">
  4. {% if ctx.flash.success %}
  5. <div class="notification is-success">
  6. {{ctx.flash.success}}
  7. </div>
  8. {% elif ctx.flash.warning %}
  9. <div class="notification is-warning">
  10. {{ctx.flash.warning}}
  11. </div>
  12. {% endif %}
  13. </div>
  14. {% endif %}

这个模板中,添加了success和warning两种提示。把它引入base.html

使用flash中间件

  1. // index.js
  2. ...
  3. const flash = require('./middlewares/flash')
  4. ...
  5. app.use(flash())
  6. ...
  7. // user.js
  8. ...
  9. signout (ctx, next) {
  10. ctx.session.user = null
  11. ctx.flash = { warning: '退出登录' }
  12. ctx.redirect('/')
  13. }
  14. ...