Appearance
Koa中间件系统
Koa的中间件系统是其核心特性之一,采用了独特的"洋葱模型"。中间件是函数,可以访问应用程序的请求和响应对象,以及应用程序请求响应周期中的下一个中间件函数。
中间件基础
中间件函数签名
javascript
// Koa中间件函数
async (ctx, next) => {
// 在下游中间件之前执行的代码
console.log('Before next()');
await next(); // 调用下一个中间件
// 在下游中间件之后执行的代码
console.log('After next()');
}
洋葱模型详解
javascript
const Koa = require('koa');
const app = new Koa();
// 中间件1
app.use(async (ctx, next) => {
console.log('1. 中间件A - 进入'); // 1. 执行
await next(); // 等待中间件B执行
console.log('5. 中间件A - 退出'); // 5. 最后执行
});
// 中间件2
app.use(async (ctx, next) => {
console.log('2. 中间件B - 进入'); // 2. 执行
await next(); // 等待最终处理
console.log('4. 中间件B - 退出'); // 4. 倒数第二执行
});
// 最终处理
app.use(async ctx => {
console.log('3. 最终处理'); // 3. 执行
ctx.body = 'Hello Koa!';
});
app.listen(3000);
// 输出顺序: 1 -> 2 -> 3 -> 4 -> 5
中间件类型
1. 基础中间件
javascript
// 日志中间件
const logger = async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
};
app.use(logger);
2. 条件中间件
javascript
// 路径特定中间件
const conditionalMiddleware = async (ctx, next) => {
if (ctx.path.startsWith('/api')) {
console.log('API请求处理');
// 只对API路径执行特定逻辑
}
await next();
};
app.use(conditionalMiddleware);
3. 参数化中间件
javascript
// 可配置的中间件工厂
const createRateLimiter = (options = {}) => {
const { maxRequests = 100, windowMs = 60000 } = options;
const requests = new Map();
return async (ctx, next) => {
const ip = ctx.ip;
const now = Date.now();
const requestHistory = requests.get(ip) || [];
// 清理过期记录
const recentRequests = requestHistory.filter(time => now - time < windowMs);
if (recentRequests.length >= maxRequests) {
ctx.status = 429;
ctx.body = { error: 'Too Many Requests' };
return;
}
recentRequests.push(now);
requests.set(ip, recentRequests);
await next();
};
};
app.use(createRateLimiter({ maxRequests: 50, windowMs: 30000 }));
常用中间件
1. 请求体解析中间件
javascript
const bodyParser = require('koa-bodyparser');
// 解析JSON和URL编码的请求体
app.use(bodyParser({
enableTypes: ['json', 'form', 'text'],
json: true,
form: true,
text: true,
encoding: 'utf-8',
multipart: false,
urlencoded: true
}));
// 使用解析后的请求体
app.use(async ctx => {
if (ctx.method === 'POST') {
const body = ctx.request.body;
ctx.body = { received: body };
}
});
2. 静态文件服务中间件
javascript
const serve = require('koa-static');
const mount = require('koa-mount');
// 服务静态文件
app.use(serve('./public'));
// 或者挂载到特定路径
app.use(mount('/assets', serve('./public')));
// 高级静态文件配置
app.use(serve('./public', {
maxage: 1000 * 60 * 60 * 24 * 7, // 7天缓存
hidden: false,
index: 'index.html',
defer: false,
gzip: true,
setHeaders: (res, path, stats) => {
// 自定义响应头
res.setHeader('X-Content-Type-Options', 'nosniff');
}
}));
3. CORS中间件
javascript
const cors = require('@koa/cors');
// 启用CORS
app.use(cors());
// 配置CORS
app.use(cors({
origin: '*',
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
maxAge: 5,
credentials: true,
allowMethods: ['GET', 'POST', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization', 'Accept']
}));
中间件组合
中间件链
javascript
// 多个中间件的组合
const middlewareChain = [
async (ctx, next) => {
console.log('Middleware 1 - Before');
await next();
console.log('Middleware 1 - After');
},
async (ctx, next) => {
console.log('Middleware 2 - Before');
await next();
console.log('Middleware 2 - After');
}
];
// 应用中间件链
middlewareChain.forEach(middleware => app.use(middleware));
中间件分组
javascript
// 创建中间件分组函数
const composeMiddlewares = (...middlewares) => {
return async (ctx, next) => {
let index = -1;
const dispatch = (i) => {
if (i <= index) return Promise.reject(new Error('next() called multiple times'));
index = i;
let fn = middlewares[i];
if (i === middlewares.length) fn = next;
if (!fn) return Promise.resolve();
try {
return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err);
}
};
return dispatch(0);
};
};
// 使用中间件分组
const authMiddlewares = composeMiddlewares(
authentication,
authorization,
permissionCheck
);
app.use(authMiddlewares);
中间件错误处理
中间件内的错误处理
javascript
const errorHandlingMiddleware = async (ctx, next) => {
try {
await next();
} catch (err) {
// 记录错误
console.error('Middleware error:', err);
// 设置响应
ctx.status = err.status || 500;
ctx.body = {
error: err.message,
timestamp: new Date().toISOString()
};
// 可选:继续传播错误
// throw err;
}
};
app.use(errorHandlingMiddleware);
全局错误处理中间件
javascript
// 全局错误处理器
const globalErrorHandler = async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
message: err.expose ? err.message : 'Internal Server Error',
...(ctx.app.env === 'development' && { stack: err.stack })
};
// 记录错误
ctx.app.emit('error', err, ctx);
}
};
app.use(globalErrorHandler);
高级中间件模式
中间件装饰器
javascript
// 中间件装饰器模式
const withTiming = (middleware) => {
return async (ctx, next) => {
const start = Date.now();
try {
await middleware(ctx, next);
} finally {
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
}
};
};
const slowOperation = async (ctx, next) => {
// 模拟耗时操作
await new Promise(resolve => setTimeout(resolve, 100));
ctx.body = 'Operation completed';
};
app.use(withTiming(slowOperation));
中间件缓存
javascript
const cacheMiddleware = (ttl = 60) => {
const cache = new Map();
return async (ctx, next) => {
const key = `${ctx.method}:${ctx.url}`;
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < ttl * 1000) {
ctx.body = cached.body;
ctx.status = cached.status;
ctx.set(cached.headers);
return;
}
await next();
// 缓存响应
cache.set(key, {
body: ctx.body,
status: ctx.status,
headers: ctx.headers,
timestamp: Date.now()
});
};
};
app.use(cacheMiddleware(300)); // 5分钟缓存
中间件最佳实践
1. 中间件顺序
javascript
// 推荐的中间件顺序
app.use(helmet()); // 安全头
app.use(cors()); // CORS
app.use(bodyParser()); // 请求体解析
app.use(logger()); // 日志记录
app.use(rateLimiter()); // 速率限制
app.use(authentication()); // 认证
app.use(authorization()); // 授权
app.use(staticFiles()); // 静态文件
app.use(routes()); // 路由
app.use(errorHandler()); // 错误处理
2. 中间件性能优化
javascript
// 避免在中间件中做重活
const efficientMiddleware = async (ctx, next) => {
// 避免每次都执行的重活
if (!ctx.path.startsWith('/api/')) {
await next();
return;
}
// 只对API路径执行重活
await next();
};
// 使用缓存避免重复计算
const cachedMiddleware = (() => {
let computedResult;
let lastComputed = 0;
const CACHE_DURATION = 5000; // 5秒
return async (ctx, next) => {
const now = Date.now();
if (!computedResult || now - lastComputed > CACHE_DURATION) {
computedResult = await expensiveCalculation();
lastComputed = now;
}
ctx.cachedValue = computedResult;
await next();
};
})();
3. 中间件测试
javascript
// 中间件测试辅助函数
const createContext = (url = '/', method = 'GET') => {
const ctx = {};
ctx.request = { url, method, headers: {} };
ctx.response = { status: 200, body: '', headers: {} };
ctx.method = method;
ctx.url = url;
return ctx;
};
// 测试中间件
const testMiddleware = async () => {
const ctx = createContext('/test', 'GET');
let nextCalled = false;
const middleware = async (ctx, next) => {
ctx.testValue = 'before';
await next();
ctx.testValue = 'after';
};
await middleware(ctx, async () => {
nextCalled = true;
ctx.body = 'processed';
});
console.assert(ctx.testValue === 'after', 'Middleware should run after next()');
console.assert(nextCalled, 'Next should be called');
console.assert(ctx.body === 'processed', 'Body should be set');
};
Koa的中间件系统通过洋葱模型提供了强大的功能和灵活性,合理使用中间件可以让应用更加模块化和可维护。