【每日一题】请说一下koa的常用中间件及其原理
人生苦短,总需要一点仪式感。比如学前端~
常用的中间件
更多中间件可点击“阅读原文”进行搜索。本篇文章只整理几个常用的中间件。
koa-router
koa-bodyparser
koa-views
koa-static
koa-session
koa-compress
koa-logger
koa-router
NPM: https://www.npmjs.com/package/koa-router
koa.js为了保持自身的精简并没有自带路由功能(express有)
为此,koa-router
提供了全面的路由功能,提供了比如类似 Express 的 app.get/post/put 的写法,支持命名参数、命名路由、支持加载多路由中间件、多路由、嵌套路由等。通过该中间件,可以非常灵活的定义路由。
const Koa = require('koa'); // 引入koa
const Router = require('koa-router'); // 引入koa-router
const app = new Koa(); // 创建koa应用
const router = new Router(); // 初始化中间件,创建路由,支持传递参数
// 指定一个url匹配
router.get('/', async (ctx) => {
// 绘制登陆页
})
router.post('/', async (ctx) => {
// 解析formData数据
})
// 加载koa-router中间件:调用router.routes()来组装匹配好的路由,返回一个合并好的中间件
app.use(router.routes());
// 对异常状态码的处理:调用router.allowedMethods()获得一个中间件,当发送了不符合的请求时,会返回 `405 Method Not Allowed` 或 `501 Not Implemented`
app.use(router.allowedMethods({
// throw: true, // 抛出错误,代替设置响应头状态
// notImplemented: () => '不支持当前请求所需要的功能',
// methodNotAllowed: () => '不支持的请求方式'
}));
// 启动服务监听本地3000端口
app.listen(3000, () => {
console.log('应用已经启动,http://localhost:3000');
})
TIPS:推荐开发者使用RESTful架构API。
koa-bodyparser
NPM: https://www.npmjs.com/package/koa-bodyparser
koa.js 并没有内置 Request Body
的解析器,当我们需要解析POST请求体的body参数时,需要使用Nodejs的req对象监听data事件来获取。过程相对繁琐
官方提供的 koa-bodyparser
中间件解决了这个问题。它用于解析请求的body,支持
他支持 Form(x-www-form-urlencoded
), Json(application/json
) ,Text(text/plain
)等格式的请求体,但不支持 form-data
的请求体,需要借助 formidable 这个库。
也可以直接使用 koa-body
或 koa-better-body
。
以下是一段官方给出的例子:
var Koa = require('koa');
var bodyParser = require('koa-bodyparser');
var app = new Koa();
app.use(bodyParser());
app.use(async ctx => {
// 解析主体会存储在 ctx.request.body 中,最终解析出来的参数是一个对象。
// 如果没有解析到任何东西,body会是一个空对象 {}
ctx.body = ctx.request.body;
});
koa-views
NPM:https://www.npmjs.com/package/koa-views
koa-views
用于加载HTML模版文件,对需要进行视图模板渲染的应用是个不可缺少的中间件,支持 ejs
, nunjucks
等众多模板引擎。
用法示例:
var views = require('koa-views');
/* 加载模块引擎 */
const render = views(__dirname + '/views', {
map: {
html: 'underscore'
}
})
app.use(render) // 必须在所有路由之前使用
/*
或者第二种写法,通过app.context展开,就没有顺序限制:
// app.context.render = render()
*/
app.use(async function (ctx) {
ctx.state = {
session: this.session,
title: 'app'
};
await ctx.render('user', {
user: 'John'
});
});
koa-static
NPM:https://www.npmjs.com/package/koa-static
专门用于加载静态资源的中间件,他可以页面请求加载css、js等静态资源。
Node.js 除了处理动态请求,也可以用作类似 Nginx
的静态文件服务,在本地开发时特别方便。可用于加载前端文件或后端 Fake 数据,可结合 koa-compress
(见下边) 和 koa-mount
使用。
const Koa = require('koa');
const server = require('koa-static'); // 引入koa-static中间件
const app = new Koa();
app.use(server(root, opts));
root:字符串类型,用于指定静态资源到相对目录
opts:对象类型,对静态资源进行详细配置
-
maxage
浏览器默认的最大缓存max-age,以毫秒为单位。默认值为0
,也就是不用缓存。 -
hidden
允许隐藏文件的传输。默认值为false
-
index
默认文件名,默认为index.html
-
defer
是否延迟响应,如果为true,则koa-static中间件将会在其他中间件执行完成后再执行。 -
gzip
当客户端支持gzip压缩,并且请求的后缀名为.gz
的文件存在时,则进行gzip压缩。默认值为true。 -
br
当客户端支持brotli,并且请求的扩展名为.br的文件存在时,尝试自动提供文件的brotli版本(注意,brotli只在https上被接受)。默认值为true。 -
setHeaders
函数用于在响应时设置自定义请求头信息,格式fn(res, path, stats)。 -
extensions
当匹配的资源没找到时,会根据该参数传入的数组参数进行依次匹配,返回匹配到的第一个资源。(默认值为false)
要设置具体看官方NPM文档。
koa-session
NPM: https://www.npmjs.com/package/koa-session
HTTP 是无状态协议,本身无法标识一次特定的会话,为了保持用户信息和状态,我们一般使用 Session
会话。koa-session
提供了这样的功能,既支持将会话信息存储在本地 Cookie,也支持存储在如 Redis,MongoDB 这样的外部存储设备。
我们就可以使用内存配合Redis做一个内存持久化存储。
koa-compress
NPM: https://www.npmjs.com/package/koa-compress
当响应体比较大时,我们一般会启用类似 Gzip
的压缩技术以减少传输内容。koa-compress
提供 了这样的功能,可根据需要进行灵活的配置。
const compress = require('koa-compress')
const Koa = require('koa')
const app = new Koa()
app.use(compress({
filter (content_type) {
return /text/i.test(content_type)
},
threshold: 2048,
gzip: {
flush: require('zlib').constants.Z_SYNC_FLUSH
},
deflate: {
flush: require('zlib').constants.Z_SYNC_FLUSH,
},
br: false // disable brotli
}))
koa-logger
NPM: https://www.npmjs.com/package/koa-logger
koa-logger
提供了输出请求日志的功能,包括请求的 url、状态码、响应时间、响应体大小等信息,对于调试和跟踪应用程序特别有帮助,koa-bunyan-logger
提供了更丰富的功能。
const logger = require('koa-logger')
const Koa = require('koa')
const app = new Koa()
app.use(logger())
中间件原理
Koa 最主要的核心是 中间件机制洋葱模型:
根据上图的形象展示,我们用代码演示一下中间件的执行顺序:
// 注册第一个中间件
app.use(async function(ctx, next){
console.log('start 1');
await next();
console.log('end 1')
});
// 注册第二个中间件
app.use(async function(ctx, next){
console.log('start 2');
await next();
console.log('end 2')
});
// 注册第三个中间件
app.use(async function(ctx, next){
console.log('start 3');
await next();
console.log('end 4')
});
最后打印顺序:
start 1
start 2
start 3
end 3
end 2
end 1
通过 use()
注册多个中间件放入数组中,然后从外层开始往内执行,遇到 next()
后暂停当前中间件、进入下一个中间件,当所有中间件执行完后,开始从最后一个中间件按顺序向前返回,依次执行中间件中next之后、未执行的部分。这个整体流程就是递归处理。
function compose(middleware) {
return () => {
// 先执行第一个函数
return dispatch(0);
function dispatch(i) {
let fn = middleware[i];
// 如何不存在直接返回 Promise
if (!fn) {
return Promise.resolve();
}
// step1: 返回一个 Promise,因此单纯变成一个 Promise 且 立即执行
// step2: 往当前中间件传入一个next()方法,当这个中间件有执行 next 的时候才执行下一个中间件
return Promise.resolve(
fn(function next() {
// 执行下一个中间件
return dispatch(i + 1);
})
);
}
};
}
核心代码是return Promise.resolve(fn(context,dispatch.bind(null, i+1)))
,递归遍历,直到遍历完所有的中间件 next
,生成一个多层嵌套的 promise
函数。
koa 的中间件处理可以当做是洋葱模型
。中间件数组中,中间件的执行是通过递归的方式来执行,调用 dispatch
函数,从第一个开始执行,当有 next 方法时创建一个 promise
,等到下一个中间件执行结果后再执行 next
后面代码。当第二个中间件也有 next
方法时,依然会创建一个新的 promise
等待下一个中间件的执行结果,这也就是中间件 next()
的执行原理
app.use()
方法将中间件 push 到中间件数组中,然后在 listen
方法中通过调用 compose
方法进行集中处理。
推荐书目
《Koa与Nodejs开发实战》
所有《每日一题》的 知识大纲索引脑图 整理在此:https://www.yuque.com/dfe_evernote/interview/everyday
你也可以点击文末的 “阅读原文” 快速跳转

让我们一起携手同走前端路!
关注公众号回复【加群】即可