Koa 基础

express 区别

  • express 源码使用的是(es5)callbackkoa 源码使用的是(es6)async/awaitkoa 源码更加简洁。
  • koa 比较小巧,可以通过扩展来扩展功能,express 内置功能比较齐全。
  • koa 没有内置路由,需要自己实现,express 内置了路由。
  • koa 没有内置模板引擎,需要自己实现,express 内置了模板引擎。

上下文(Context)

  • koa 提供了一个Context对象,表示一次对话的上下文(包括HTTP请求和HTTP回复)。

  • 通过加工这个对象,就可以控制返回给用户的内容。

  • koa 的实现其实就是封装了 http 模块,实现了一个全新的 ctx 上下文,该上下文中有原生的 req,res,也有 koa 自己封装的 request, response。

app.use(async (ctx) => {
  ctx // 这是 Context
  ctx.request // 这是 koa Request
  ctx.response // 这是 koa Response
})
  • Context 隔离问题

Context 隔离问题

请求

  • koanoderequest 对象封装到 ctx.request 中,没有直接将 request 对象暴露给用户,而是提供了一些常用的方法。
    • ctx.request.method:获取请求方法
    • ctx.request.url:获取请求地址
    • ctx.request.path:获取请求路径
    • ctx.request.query:获取查询字符串参数
    • ctx.request.querystring:获取查询字符串
    • ctx.request.headers:获取请求头
    • ctx.request.host:获取请求域名
    • ctx.request.body:获取请求体
    • ctx.request.files:获取上传的文件
    • ctx.request.get():获取请求头
    • ctx.request.is():判断请求头的 Content-Type 类型
    • ctx.request.set():设置响应头
    • ctx.request.type:获取请求类型
    • ctx.request.accepts():检查请求类型

响应

  • koanoderesponse 对象封装到 ctx.response 中,没有直接将 response 对象暴露给用户,而是提供了一些常用的方法。
    • ctx.response.body:设置响应体
    • ctx.response.status:设置响应状态码
    • ctx.response.set():设置响应头
    • ctx.response.redirect():重定向
    • ctx.response.attachment():设置响应头 Content-Disposition 为 attachment
    • ctx.response.type:设置响应 Content-Type
    • ctx.response.lastModified:设置响应头 Last-Modified
    • ctx.response.etag:设置响应头 ETag
    • ctx.response.get():获取响应头
    • ctx.response.is():判断响应头的 Content-Type 类型

中间件

  • 原理

    • koa 中间件机制就是函数组合的概念,将一组需要顺序执行的函数复合为一个函数,外层函数的参数实际上就是内层函数的返回值。

    • 使用 use 来订阅中间件,使用 callback 来执行中间件。

      app.use(async (ctx, next) => {
        // do something
        await next()
        // do something
      })
      
      app.use(async (ctx, next) => {
        // do something
        await next()
        // do something
      })
      
      // 相当于订阅
      use(fn) {
      this.middlewares = []
      this.middlewares.push(fn)
      }
      
    • 首先将每个订阅的中间件存起来。

    • 等待请求来的时候再处理,思路就是: 封装一个 dispatch 函数,然后将采用递归,从第一个中间件开始执行,将 dispatch 函数作为 next 传入,索引+1,当第一个中间件执行 next 的时候,就会执行 dispatch,执行第二个中间件,以此类推,直到所有中间件执行完毕。并且每个中间件都必须是返回 pormise 的函数,这样才能使用 await。

    中间件 compose

  • koa 重要的是一个中间件的处理,koa 支持了 async + await 的中间件,像洋葱一样,可以等待中间件执行完毕之后往下走,而 express 不可以。

  • 实现思路就是将所有中间件整合成一个 promise,然后从第一个中间件开始执行,索引+1,调用 next 的时候又会执行第二个中间件,索引+1,直到最后调用完毕,而且每个中间件都会被包装成 promise.resolve 去执行,这样就能搭配 asyncawait,等到所有中间件执行完毕又会一层一层往回执行,就相当于洋葱模型一样。

    // 洋葱模型
    function compose(middlewares) {
      return function (ctx) {
        return dispatch(0)
        function dispatch(i) {
          let fn = middlewares[i]
          if (!fn) {
            return Promise.resolve()
          }
          return Promise.resolve(
            fn(ctx, function next() {
              return dispatch(i + 1)
            })
          )
        }
      }
    }
    

写一个简单中间件

// 中间件一般都是返回一个函数,采用柯里化。
function bodyParser() {
  return async (ctx, next) => {
    await new Promise((resolve, reject) => {
      let arr = []
      ctx.req.on('data', function (chunk) {
        arr.push(chunk)
      })
      ctx.req.on('end', function () {
        ctx.request.body = Buffer.concat(arr).toString()
        resolve()
      })
    })
    // 执行下一个中间件
    await next()
  }
}

参考资料