Skip to content
On this page

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的中间件系统通过洋葱模型提供了强大的功能和灵活性,合理使用中间件可以让应用更加模块化和可维护。