Koa 基础
与express 区别
express源码使用的是(es5)callback,koa源码使用的是(es6)async/await,koa源码更加简洁。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 隔离问题

请求
koa将node的request对象封装到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():检查请求类型
响应
koa将node的response对象封装到ctx.response中,没有直接将response对象暴露给用户,而是提供了一些常用的方法。ctx.response.body:设置响应体ctx.response.status:设置响应状态码ctx.response.set():设置响应头ctx.response.redirect():重定向ctx.response.attachment():设置响应头 Content-Disposition 为 attachmentctx.response.type:设置响应 Content-Typectx.response.lastModified:设置响应头 Last-Modifiedctx.response.etag:设置响应头 ETagctx.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。

koa 重要的是一个中间件的处理,koa 支持了 async + await 的中间件,像洋葱一样,可以等待中间件执行完毕之后往下走,而 express 不可以。
实现思路就是将所有中间件整合成一个 promise,然后从第一个中间件开始执行,索引+1,调用 next 的时候又会执行第二个中间件,索引+1,直到最后调用完毕,而且每个中间件都会被包装成
promise.resolve去执行,这样就能搭配async和await,等到所有中间件执行完毕又会一层一层往回执行,就相当于洋葱模型一样。// 洋葱模型 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()
}
}
