中间件的基本结构与编写规范
中间件的基本结构
Koa2中间件本质上是异步函数,接收两个参数:ctx和next。ctx封装了请求和响应对象,next代表下一个中间件的执行权。中间件通过async/await语法处理异步操作,形成洋葱模型调用链。
async function middleware(ctx, next) {
// 前置操作
console.log('Before next()');
await next(); // 执行下游中间件
// 后置操作
console.log('After next()');
}
典型结构包含三个阶段:
- 请求预处理:修改ctx状态或拦截请求
- next()调用:将控制权移交下游
- 响应后处理:修改响应数据或记录日志
编写规范
单一职责原则
每个中间件应只处理特定功能。例如身份验证中间件不应包含业务逻辑:
// 好的实践
async function auth(ctx, next) {
const token = ctx.headers['authorization'];
if (!verifyToken(token)) {
ctx.throw(401, 'Unauthorized');
}
await next();
}
// 反模式
async function authAndProcess(ctx, next) {
// 混合认证与业务处理
const token = ctx.headers['authorization'];
if (verifyToken(token)) {
ctx.user = getUser(token);
await processBusiness(ctx); // 业务逻辑混入
}
await next();
}
错误处理规范
同步错误自动捕获,异步错误需显式处理:
// 错误处理中间件示例
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: err.message,
code: err.code
};
ctx.app.emit('error', err, ctx); // 触发应用级错误事件
}
});
// 业务中间件抛出错误
app.use(async ctx => {
if (!ctx.query.requiredParam) {
const err = new Error('Missing parameter');
err.code = 'MISSING_PARAM';
err.status = 400;
throw err;
}
});
性能优化要点
- 避免阻塞操作:长时间同步任务应转为异步
- 合理使用缓存:高频操作结果可缓存
- 控制中间件数量:每个请求都会遍历整个中间件栈
// 缓存示例
const cache = new Map();
app.use(async (ctx, next) => {
const key = ctx.url;
if (cache.has(key)) {
ctx.body = cache.get(key);
return;
}
await next();
if (ctx.status === 200) {
cache.set(key, ctx.body);
}
});
高级模式
中间件工厂函数
通过闭包创建可配置中间件:
function logger(format = ':method :url') {
return async (ctx, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
console.log(format
.replace(':method', ctx.method)
.replace(':url', ctx.url)
.replace(':duration', duration)
);
};
}
app.use(logger());
app.use(logger(':method :url - :duration ms'));
组合中间件
使用koa-compose创建子中间件栈:
const compose = require('koa-compose');
const middleware1 = async (ctx, next) => {
console.log('Middleware 1 start');
await next();
console.log('Middleware 1 end');
};
const middleware2 = async (ctx, next) => {
console.log('Middleware 2 start');
await next();
console.log('Middleware 2 end');
};
const combined = compose([middleware1, middleware2]);
app.use(combined);
响应时间扩展
通过上下文原型扩展自定义方法:
// 扩展ctx原型
app.context.formatResponse = function(data) {
this.type = 'application/json';
this.body = {
timestamp: Date.now(),
data: data,
status: this.status
};
};
// 使用扩展方法
app.use(async ctx => {
const users = await User.findAll();
ctx.formatResponse(users);
});
调试与测试
调试技巧
- 使用debug模块标记中间件
- 插入调试中间件检查上下文状态
const debug = require('debug')('koa:middleware');
app.use(async (ctx, next) => {
debug('Request headers:', ctx.headers);
const start = Date.now();
await next();
debug(`Response time: ${Date.now() - start}ms`);
});
单元测试示例
使用supertest测试中间件行为:
const request = require('supertest');
const Koa = require('koa');
describe('Auth Middleware', () => {
it('should 401 without token', async () => {
const app = new Koa();
app.use(authMiddleware);
app.use(ctx => { ctx.body = 'OK' });
await request(app.callback())
.get('/')
.expect(401);
});
});
最佳实践
配置管理
通过app.context共享配置:
// 初始化时
app.context.config = {
maxRequests: 1000,
timeout: 3000
};
// 中间件中使用
app.use(async (ctx, next) => {
if (ctx.request.count > ctx.config.maxRequests) {
ctx.throw(429);
}
await next();
});
流量控制实现
令牌桶算法中间件示例:
class TokenBucket {
constructor(rate, capacity) {
this.tokens = capacity;
this.rate = rate;
this.lastFill = Date.now();
}
take() {
this.refill();
if (this.tokens < 1) return false;
this.tokens -= 1;
return true;
}
refill() {
const now = Date.now();
const elapsed = (now - this.lastFill) / 1000;
this.tokens = Math.min(
this.capacity,
this.tokens + elapsed * this.rate
);
this.lastFill = now;
}
}
// 使用令牌桶中间件
const bucket = new TokenBucket(10, 20); // 10 tokens/second, 20 max
app.use(async (ctx, next) => {
if (!bucket.take()) {
ctx.status = 429;
ctx.body = 'Too Many Requests';
return;
}
await next();
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:性能监控工具的初步设置
下一篇:同步与异步中间件的区别