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()
}
}