koa2中间件开发

koa2 中间件机制

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

下图为经典的Koa洋葱模型

koa2洋葱模型

看看官网的经典示例:

const Koa = require('koa')
const app = new Koa()

// x-response-time
app.use(async (ctx, next) => {
  const start = Date.now()
  await next()
  const ms = Date.now() - start
  ctx.set('X-Response-Time', `${ms}ms`)
})

// logger
app.use(async (ctx, next) => {
  const start = Date.now()
  await next()
  const ms = Date.now() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}`)
})

// response
app.use(async ctx => {
  ctx.body = 'Hello World'
})

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注册,同时为该函数传入 ctx 和 next 两个参数。

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

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

// middleware/logger.js
module.exports = function () {
  return async function ( ctx, next ) {
    console.log( ctx.method, ctx.header.host + ctx.url )
    await next()
  }
}

消息闪现中间件

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

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

module.exports = function flash (opts) {
  let key = 'flash'
  return async (ctx, next) => {
    if (ctx.session === undefined) throw new Error('ctx.flash requires sessions')
    let data = ctx.session[key]
    ctx.session[key] = null
    Object.defineProperty(ctx, 'flash', {
      enumerable: true,
      get: () => data,
      set: (val) => {
        ctx.session[key] = val
      }
    })
    await next()
  }
}

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

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

// components/notification.html

{% if ctx.flash %}
<div class="notifications">
    {% if ctx.flash.success %}
    <div class="notification is-success">
        {{ctx.flash.success}}
    </div>
    {% elif  ctx.flash.warning %}
    <div class="notification is-warning">
        {{ctx.flash.warning}}
    </div>
    {% endif %}
</div>
{% endif %}

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

使用flash中间件

// index.js
...
const flash = require('./middlewares/flash')
...
app.use(flash())
...

// user.js
...
signout (ctx, next) {
    ctx.session.user = null
    ctx.flash = { warning: '退出登录' }
    ctx.redirect('/')
}
...