跳到主要内容

服务端框架✅

本主题涵盖Node.js服务端开发中的主流框架,包括Express、Koa等框架的核心概念和使用方法。

Express中间件(Middleware)的工作原理是什么?

答案

核心概念

Express中间件是处理HTTP请求的函数,它们按照定义的顺序依次执行,形成一个处理链。中间件可以执行代码、修改请求和响应对象、结束请求-响应循环,或调用下一个中间件。

工作原理

Express中间件基于**责任链模式(Chain of Responsibility Pattern)**实现:

1. 中间件函数签名

function middleware(req, res, next) {
// 执行业务逻辑
// 修改req或res对象
next(); // 调用下一个中间件
}

2. 执行流程

  • 请求进入→第一个中间件→执行逻辑→调用next()→下一个中间件→...→响应返回
  • 如果某个中间件不调用next(),请求处理链就会中断
  • 错误处理中间件有4个参数:(err, req, res, next)

3. 中间件类型

  • 应用级中间件: app.use()
  • 路由级中间件: router.use()
  • 错误处理中间件: 4参数函数
  • 内置中间件: express.static(), express.json()
  • 第三方中间件: morgan, cors

示例代码:

// Express 中间件工作原理演示
const express = require('express');
const app = express();

// 模拟数据库
const users = [
  { id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin' },
  { id: 2, name: 'Bob', email: 'bob@example.com', role: 'user' },
  { id: 3, name: 'Charlie', email: 'charlie@example.com', role: 'user' }
];

/**
 * 1. 基础中间件示例
 */
function basicLoggingMiddleware(req, res, next) {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next(); // 必须调用next()传递控制权
}

/**
 * 2. 认证中间件 - 责任链模式实现
 */
function authenticate(req, res, next) {
  const token = req.headers.authorization;
  
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }
  
  // 模拟token验证
  if (token === 'Bearer valid-token') {
    req.user = { id: 1, name: 'Alice', role: 'admin' };
    next(); // 验证通过,继续下一个中间件
  } else {
    res.status(401).json({ error: 'Invalid token' });
  }
}

/**
 * 3. 权限检查中间件工厂函数
 */
function authorize(requiredRole) {
  return function(req, res, next) {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }
    
    if (req.user.role !== requiredRole && req.user.role !== 'admin') {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    
    next();
  };
}

/**
 * 4. 错误处理中间件
 */
function errorHandler(err, req, res, next) {
  console.error('Error occurred:', err);
  
  // 如果响应已经发送,将错误传递给默认的Express错误处理器
  if (res.headersSent) {
    return next(err);
  }
  
  // 根据错误类型返回不同的状态码
  let statusCode = 500;
  let message = 'Internal Server Error';
  
  if (err.name === 'ValidationError') {
    statusCode = 400;
    message = err.message;
  } else if (err.name === 'UnauthorizedError') {
    statusCode = 401;
    message = 'Unauthorized';
  }
  
  res.status(statusCode).json({
    error: message,
    timestamp: new Date().toISOString(),
    path: req.path
  });
}

/**
 * 5. 请求验证中间件
 */
function validateUserInput(req, res, next) {
  const { name, email } = req.body;
  
  if (!name || typeof name !== 'string' || name.trim().length === 0) {
    const error = new Error('Name is required and must be a non-empty string');
    error.name = 'ValidationError';
    return next(error);
  }
  
  if (!email || !email.includes('@')) {
    const error = new Error('Valid email is required');
    error.name = 'ValidationError';
    return next(error);
  }
  
  // 验证通过,继续执行
  next();
}

/**
 * 6. 响应时间统计中间件
 */
function responseTime(req, res, next) {
  const start = Date.now();
  
  // 监听响应完成事件
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`Request to ${req.path} took ${duration}ms`);
  });
  
  next();
}

/**
 * 7. CORS中间件实现
 */
function corsMiddleware(options = {}) {
  const {
    origin = '*',
    methods = 'GET,HEAD,PUT,PATCH,POST,DELETE',
    allowedHeaders = 'Content-Type,Authorization',
    credentials = false
  } = options;
  
  return function(req, res, next) {
    // 设置CORS头
    res.header('Access-Control-Allow-Origin', origin);
    res.header('Access-Control-Allow-Methods', methods);
    res.header('Access-Control-Allow-Headers', allowedHeaders);
    
    if (credentials) {
      res.header('Access-Control-Allow-Credentials', 'true');
    }
    
    // 处理预检请求
    if (req.method === 'OPTIONS') {
      return res.sendStatus(200);
    }
    
    next();
  };
}

/**
 * 8. 缓存中间件
 */
const cache = new Map();

function cacheMiddleware(duration = 300000) { // 默认5分钟
  return function(req, res, next) {
    // 只缓存GET请求
    if (req.method !== 'GET') {
      return next();
    }
    
    const key = req.originalUrl;
    const cached = cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < duration) {
      console.log(`Cache hit for ${key}`);
      return res.json(cached.data);
    }
    
    // 保存原始的res.json方法
    const originalJson = res.json;
    
    // 重写res.json方法以缓存响应
    res.json = function(data) {
      cache.set(key, {
        data: data,
        timestamp: Date.now()
      });
      
      console.log(`Cached response for ${key}`);
      return originalJson.call(this, data);
    };
    
    next();
  };
}

/**
 * 9. 中间件执行顺序演示
 */
function middlewareOrderDemo() {
  console.log('=== Express 中间件执行顺序演示 ===');
  
  // 全局中间件 - 按顺序执行
  app.use(corsMiddleware());
  app.use(responseTime);
  app.use(basicLoggingMiddleware);
  app.use(express.json()); // 解析JSON请求体
  
  // 路由级中间件
  app.get('/users', 
    cacheMiddleware(60000), // 缓存1分钟
    (req, res) => {
      res.json(users);
    }
  );
  
  // 需要认证的路由
  app.get('/profile',
    authenticate,
    (req, res) => {
      res.json(req.user);
    }
  );
  
  // 需要管理员权限的路由
  app.delete('/users/:id',
    authenticate,
    authorize('admin'),
    (req, res) => {
      const userId = parseInt(req.params.id);
      const userIndex = users.findIndex(u => u.id === userId);
      
      if (userIndex === -1) {
        return res.status(404).json({ error: 'User not found' });
      }
      
      users.splice(userIndex, 1);
      res.json({ message: 'User deleted successfully' });
    }
  );
  
  // 带验证的POST路由
  app.post('/users',
    authenticate,
    authorize('admin'),
    validateUserInput,
    (req, res) => {
      const newUser = {
        id: users.length + 1,
        name: req.body.name,
        email: req.body.email,
        role: req.body.role || 'user'
      };
      
      users.push(newUser);
      res.status(201).json(newUser);
    }
  );
  
  // 触发错误的路由
  app.get('/error', (req, res, next) => {
    const error = new Error('This is a test error');
    error.name = 'TestError';
    next(error); // 传递错误给错误处理中间件
  });
  
  // 错误处理中间件必须放在最后
  app.use(errorHandler);
  
  // 404处理
  app.use('*', (req, res) => {
    res.status(404).json({
      error: 'Route not found',
      path: req.originalUrl
    });
  });
}

/**
 * 10. 自定义中间件类 - 更高级的封装
 */
class MiddlewareManager {
  constructor() {
    this.middlewares = [];
  }
  
  use(middleware) {
    this.middlewares.push(middleware);
    return this;
  }
  
  // 创建一个处理函数,按顺序执行所有中间件
  getHandler() {
    return (req, res) => {
      let index = 0;
      
      const next = (error) => {
        if (error) {
          // 查找错误处理中间件
          const errorMiddleware = this.middlewares.find(mw => mw.length === 4);
          if (errorMiddleware) {
            return errorMiddleware(error, req, res, () => {});
          }
          throw error;
        }
        
        if (index >= this.middlewares.length) {
          return; // 所有中间件执行完毕
        }
        
        const middleware = this.middlewares[index++];
        
        try {
          if (middleware.length === 4) {
            // 跳过错误处理中间件
            next();
          } else {
            middleware(req, res, next);
          }
        } catch (err) {
          next(err);
        }
      };
      
      next();
    };
  }
}

/**
 * 演示自定义中间件管理器
 */
function demonstrateMiddlewareManager() {
  const manager = new MiddlewareManager();
  
  manager
    .use((req, res, next) => {
      console.log('Middleware 1: Request received');
      next();
    })
    .use((req, res, next) => {
      console.log('Middleware 2: Processing request');
      req.processed = true;
      next();
    })
    .use((req, res, next) => {
      console.log('Middleware 3: Finalizing');
      res.json({ processed: req.processed });
    });
  
  return manager.getHandler();
}

// 启动演示服务器
function startServer() {
  middlewareOrderDemo();
  
  // 自定义路由使用中间件管理器
  const customHandler = demonstrateMiddlewareManager();
  app.get('/custom', customHandler);
  
  const PORT = process.env.PORT || 3000;
  
  const server = app.listen(PORT, () => {
    console.log(`🚀 Express middleware demo server started on port ${PORT}`);
    console.log('\n📝 可以测试的路由:');
    console.log(`  GET  http://localhost:${PORT}/users`);
    console.log(`  GET  http://localhost:${PORT}/profile (需要 Authorization: Bearer valid-token)`);
    console.log(`  POST http://localhost:${PORT}/users (需要认证和管理员权限)`);
    console.log(`  DELETE http://localhost:${PORT}/users/1 (需要认证和管理员权限)`);
    console.log(`  GET  http://localhost:${PORT}/error (测试错误处理)`);
    console.log(`  GET  http://localhost:${PORT}/custom (自定义中间件管理器)`);
    console.log('\n💡 测试命令示例:');
    console.log('  curl http://localhost:3000/users');
    console.log('  curl -H "Authorization: Bearer valid-token" http://localhost:3000/profile');
    
    // 演示完成后自动关闭服务器
    setTimeout(() => {
      console.log('\n✅ 演示完成,关闭服务器');
      server.close();
    }, 30000);
  });
  
  return server;
}

// 模拟中间件测试
function simulateMiddlewareFlow() {
  console.log('\n=== 中间件执行流程模拟 ===');
  
  // 模拟请求对象
  const mockReq = {
    method: 'GET',
    url: '/users',
    headers: { 'authorization': 'Bearer valid-token' },
    user: null
  };
  
  // 模拟响应对象
  const mockRes = {
    status: (code) => {
      mockRes.statusCode = code;
      return mockRes;
    },
    json: (data) => {
      console.log(`响应 [${mockRes.statusCode || 200}]:`, JSON.stringify(data, null, 2));
      return mockRes;
    }
  };
  
  // 执行中间件链
  console.log('1. 开始执行中间件链...');
  
  basicLoggingMiddleware(mockReq, mockRes, () => {
    console.log('2. 日志中间件执行完成');
    
    authenticate(mockReq, mockRes, () => {
      console.log('3. 认证中间件执行完成,用户:', mockReq.user);
      
      // 模拟路由处理器
      console.log('4. 执行路由处理器');
      mockRes.json({ message: '中间件链执行成功', user: mockReq.user });
    });
  });
}

// 如果直接运行此文件
if (require.main === module) {
  console.log('Express 中间件工作原理演示');
  console.log('================================');
  
  // 首先运行模拟演示
  simulateMiddlewareFlow();
  
  // 然后启动实际服务器
  setTimeout(() => {
    startServer();
  }, 2000);
}

module.exports = {
  basicLoggingMiddleware,
  authenticate,
  authorize,
  errorHandler,
  validateUserInput,
  responseTime,
  corsMiddleware,
  cacheMiddleware,
  MiddlewareManager,
  startServer,
  simulateMiddlewareFlow
};

面试官视角:

该题考察候选人对Express核心机制的理解:

  • 要点清单: 理解责任链模式;掌握中间件执行顺序;了解错误处理机制;能自定义中间件
  • 加分项: 有实际项目经验;了解性能优化;能设计复用性强的中间件;理解异步中间件
  • 常见失误: 不理解next()作用;搞错中间件顺序;不了解错误处理;缺乏实践经验

延伸阅读:

Koa洋葱模型的原理和实现是什么?

答案

核心概念

Koa的洋葱模型是指中间件的执行流程像剥洋葱一样,从外层到内层,再从内层回到外层。这种模式让异步流程控制变得更加直观和可预测。

洋葱模型特点

1. 执行顺序

中间件1进入 → 中间件2进入 → 中间件3进入 → 中间件3退出 → 中间件2退出 → 中间件1退出

2. async/await支持

  • 天然支持异步操作
  • 可以等待下游中间件执行完成
  • 错误会自动向上冒泡

3. 上下文共享

  • 所有中间件共享ctx对象
  • 可以在ctx.state中存储状态
  • 支持请求和响应的统一处理

实现原理:

// Koa 洋葱模型和异常处理演示
const Koa = require('koa');
const compose = require('koa-compose');

/**
 * 1. 洋葱模型基础演示
 */
function demonstrateOnionModel() {
  console.log('=== Koa 洋葱模型演示 ===');
  
  const app = new Koa();
  
  // 中间件1 - 外层
  app.use(async (ctx, next) => {
    console.log('中间件1 - 进入');
    const start = Date.now();
    
    await next(); // 等待下一个中间件执行完成
    
    const duration = Date.now() - start;
    console.log('中间件1 - 退出');
    console.log(`请求处理时间: ${duration}ms`);
    
    // 在响应头中添加处理时间
    ctx.set('X-Response-Time', `${duration}ms`);
  });
  
  // 中间件2 - 中层
  app.use(async (ctx, next) => {
    console.log('中间件2 - 进入');
    
    // 添加请求日志信息
    ctx.state.requestInfo = {
      method: ctx.method,
      url: ctx.url,
      timestamp: new Date().toISOString()
    };
    
    await next();
    
    console.log('中间件2 - 退出');
    console.log('请求信息:', ctx.state.requestInfo);
  });
  
  // 中间件3 - 内层
  app.use(async (ctx, next) => {
    console.log('中间件3 - 进入');
    
    // 模拟异步操作
    await new Promise(resolve => setTimeout(resolve, 100));
    
    await next();
    
    console.log('中间件3 - 退出');
  });
  
  // 路由处理器 - 最内层
  app.use(async (ctx) => {
    console.log('路由处理器 - 处理请求');
    
    ctx.body = {
      message: '洋葱模型演示',
      path: ctx.path,
      method: ctx.method,
      requestInfo: ctx.state.requestInfo
    };
    
    console.log('路由处理器 - 响应已设置');
  });
  
  return app;
}

/**
 * 2. 不使用async/await的洋葱模型实现 (Generator版本)
 */
function demonstrateGeneratorOnionModel() {
  console.log('\n=== Generator版本洋葱模型 ===');
  
  // 简化的Koa实现,使用Generator
  class SimpleKoa {
    constructor() {
      this.middlewares = [];
    }
    
    use(middleware) {
      this.middlewares.push(middleware);
      return this;
    }
    
    // 使用Generator实现的compose函数
    compose(middlewares) {
      return function* (next) {
        let index = -1;
        
        function* dispatch(i) {
          if (i <= index) {
            throw new Error('next() called multiple times');
          }
          index = i;
          
          const middleware = middlewares[i];
          if (!middleware) {
            if (next) yield* next;
            return;
          }
          
          yield* middleware(dispatch.bind(null, i + 1));
        }
        
        yield* dispatch(0);
      };
    }
    
    // 模拟请求处理
    handleRequest(ctx) {
      const composed = this.compose(this.middlewares);
      const generator = composed.call(ctx);
      
      // 手动执行generator
      function runGenerator(gen) {
        const result = gen.next();
        if (!result.done) {
          return runGenerator(gen);
        }
        return result.value;
      }
      
      return runGenerator(generator);
    }
  }
  
  const app = new SimpleKoa();
  
  // Generator中间件
  app.use(function* (next) {
    console.log('Generator中间件1 - 进入');
    yield* next;
    console.log('Generator中间件1 - 退出');
  });
  
  app.use(function* (next) {
    console.log('Generator中间件2 - 进入');
    yield* next;
    console.log('Generator中间件2 - 退出');
  });
  
  app.use(function* () {
    console.log('Generator路由处理器');
    this.body = 'Generator洋葱模型演示';
  });
  
  // 模拟请求
  const ctx = { method: 'GET', url: '/' };
  app.handleRequest(ctx);
  console.log('响应:', ctx.body);
  
  return app;
}

/**
 * 3. Koa异常处理演示
 */
function demonstrateErrorHandling() {
  console.log('\n=== Koa 异常处理演示 ===');
  
  const app = new Koa();
  
  // 全局错误处理中间件
  app.use(async (ctx, next) => {
    try {
      await next();
    } catch (error) {
      console.error('捕获到错误:', error.message);
      
      // 设置错误响应
      ctx.status = error.status || 500;
      ctx.body = {
        error: error.message,
        status: ctx.status,
        timestamp: new Date().toISOString()
      };
      
      // 触发应用级错误事件
      ctx.app.emit('error', error, ctx);
    }
  });
  
  // 请求日志中间件
  app.use(async (ctx, next) => {
    console.log(`收到请求: ${ctx.method} ${ctx.url}`);
    
    try {
      await next();
      console.log(`响应状态: ${ctx.status}`);
    } catch (error) {
      console.log(`请求处理失败: ${error.message}`);
      throw error; // 重新抛出错误让上层处理
    }
  });
  
  // 业务中间件 - 可能抛出错误
  app.use(async (ctx, next) => {
    if (ctx.path === '/error') {
      // 模拟业务错误
      const error = new Error('这是一个业务错误');
      error.status = 400;
      throw error;
    }
    
    if (ctx.path === '/server-error') {
      // 模拟服务器错误
      throw new Error('服务器内部错误');
    }
    
    await next();
  });
  
  // 正常路由
  app.use(async (ctx) => {
    if (ctx.path === '/') {
      ctx.body = { message: '正常响应' };
    } else {
      ctx.status = 404;
      ctx.body = { error: '路由不存在' };
    }
  });
  
  // 监听应用级错误事件
  app.on('error', (error, ctx) => {
    console.log('应用级错误处理:', {
      error: error.message,
      url: ctx?.url,
      method: ctx?.method,
      status: ctx?.status
    });
  });
  
  return app;
}

/**
 * 4. Koa中间件组合和复用
 */
function demonstrateMiddlewareComposition() {
  console.log('\n=== Koa 中间件组合演示 ===');
  
  // 创建可复用的中间件组
  const authMiddleware = async (ctx, next) => {
    const token = ctx.headers.authorization;
    
    if (!token) {
      ctx.status = 401;
      ctx.body = { error: 'No authorization token' };
      return;
    }
    
    if (token === 'Bearer valid-token') {
      ctx.state.user = { id: 1, name: 'Alice' };
      await next();
    } else {
      ctx.status = 401;
      ctx.body = { error: 'Invalid token' };
    }
  };
  
  const validationMiddleware = (schema) => {
    return async (ctx, next) => {
      const { error, value } = schema.validate(ctx.request.body);
      
      if (error) {
        ctx.status = 400;
        ctx.body = { error: error.details[0].message };
        return;
      }
      
      ctx.state.validatedData = value;
      await next();
    };
  };
  
  const loggingMiddleware = async (ctx, next) => {
    const start = Date.now();
    console.log(`-> ${ctx.method} ${ctx.url}`);
    
    await next();
    
    const duration = Date.now() - start;
    console.log(`<- ${ctx.status} (${duration}ms)`);
  };
  
  // 组合中间件
  const apiMiddlewares = compose([
    loggingMiddleware,
    authMiddleware,
    async (ctx, next) => {
      console.log('API中间件执行');
      await next();
    }
  ]);
  
  const app = new Koa();
  
  // 基础路由
  app.use(async (ctx, next) => {
    if (ctx.path === '/public') {
      ctx.body = { message: '公开接口' };
      return;
    }
    await next();
  });
  
  // 需要认证的API路由
  app.use(async (ctx, next) => {
    if (ctx.path.startsWith('/api')) {
      await apiMiddlewares(ctx, next);
    } else {
      await next();
    }
  });
  
  // API路由处理
  app.use(async (ctx) => {
    if (ctx.path === '/api/user') {
      ctx.body = {
        message: '认证成功',
        user: ctx.state.user
      };
    } else {
      ctx.status = 404;
      ctx.body = { error: 'Route not found' };
    }
  });
  
  return app;
}

/**
 * 5. 手动实现简版的koa-compose
 */
function createCompose() {
  return function compose(middleware) {
    if (!Array.isArray(middleware)) {
      throw new TypeError('Middleware stack must be an array!');
    }
    
    for (const fn of middleware) {
      if (typeof fn !== 'function') {
        throw new TypeError('Middleware must be composed of functions!');
      }
    }
    
    return function composedMiddleware(context, next) {
      let index = -1;
      
      function dispatch(i) {
        if (i <= index) {
          return Promise.reject(new Error('next() called multiple times'));
        }
        index = i;
        
        let fn = middleware[i];
        
        if (i === middleware.length) {
          fn = next;
        }
        
        if (!fn) {
          return Promise.resolve();
        }
        
        try {
          return Promise.resolve(fn(context, () => dispatch(i + 1)));
        } catch (err) {
          return Promise.reject(err);
        }
      }
      
      return dispatch(0);
    };
  };
}

/**
 * 6. 演示自定义compose的使用
 */
function demonstrateCustomCompose() {
  console.log('\n=== 自定义Compose演示 ===');
  
  const compose = createCompose();
  
  const middleware1 = async (ctx, next) => {
    console.log('自定义中间件1 - 开始');
    ctx.count = (ctx.count || 0) + 1;
    await next();
    console.log('自定义中间件1 - 结束');
  };
  
  const middleware2 = async (ctx, next) => {
    console.log('自定义中间件2 - 开始');
    ctx.count = (ctx.count || 0) + 1;
    await next();
    console.log('自定义中间件2 - 结束');
  };
  
  const middleware3 = async (ctx, next) => {
    console.log('自定义中间件3 - 开始');
    ctx.count = (ctx.count || 0) + 1;
    console.log(`中间件执行次数: ${ctx.count}`);
    console.log('自定义中间件3 - 结束');
  };
  
  const composedFn = compose([middleware1, middleware2, middleware3]);
  
  // 执行组合后的中间件
  const ctx = {};
  composedFn(ctx).then(() => {
    console.log('所有中间件执行完成');
    console.log('最终ctx:', ctx);
  });
}

/**
 * 7. 完整的Koa应用演示
 */
async function runCompleteDemo() {
  console.log('\n=== 完整Koa应用演示 ===');
  
  const app = demonstrateOnionModel();
  const errorApp = demonstrateErrorHandling();
  const compositionApp = demonstrateMiddlewareComposition();
  
  // 模拟请求处理
  console.log('\n1. 模拟正常请求:');
  await simulateRequest(app, { method: 'GET', url: '/' });
  
  console.log('\n2. 模拟错误请求:');
  await simulateRequest(errorApp, { method: 'GET', url: '/error' });
  
  console.log('\n3. 模拟认证请求:');
  await simulateRequest(compositionApp, { 
    method: 'GET', 
    url: '/api/user',
    headers: { authorization: 'Bearer valid-token' }
  });
}

/**
 * 模拟请求处理函数
 */
async function simulateRequest(app, request) {
  const ctx = {
    method: request.method,
    url: request.url,
    path: new URL(request.url, 'http://localhost').pathname,
    headers: request.headers || {},
    set: function(key, value) {
      this.headers[key.toLowerCase()] = value;
    },
    state: {},
    status: 200,
    body: null
  };
  
  try {
    // 获取应用的中间件并执行
    const composed = app.middleware.length > 0 
      ? compose(app.middleware) 
      : async (ctx) => { ctx.body = 'No middleware'; };
    
    await composed(ctx);
    
    console.log(`响应 [${ctx.status}]:`, JSON.stringify(ctx.body, null, 2));
  } catch (error) {
    console.log(`请求失败:`, error.message);
  }
}

// 主演示函数
async function main() {
  console.log('Koa洋葱模型和异常处理演示');
  console.log('===============================');
  
  // 1. 基础洋葱模型
  demonstrateOnionModel();
  
  // 2. Generator版本
  setTimeout(() => {
    demonstrateGeneratorOnionModel();
  }, 1000);
  
  // 3. 异常处理
  setTimeout(() => {
    demonstrateErrorHandling();
  }, 2000);
  
  // 4. 中间件组合
  setTimeout(() => {
    demonstrateMiddlewareComposition();
  }, 3000);
  
  // 5. 自定义compose
  setTimeout(() => {
    demonstrateCustomCompose();
  }, 4000);
  
  // 6. 完整演示
  setTimeout(() => {
    runCompleteDemo();
  }, 5000);
}

// 如果直接运行此文件
if (require.main === module) {
  main().catch(console.error);
}

module.exports = {
  demonstrateOnionModel,
  demonstrateGeneratorOnionModel,
  demonstrateErrorHandling,
  demonstrateMiddlewareComposition,
  createCompose,
  demonstrateCustomCompose,
  simulateRequest
};

异常处理:

Koa的异常处理基于Promise,错误会自动向上传播:

app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = err.message;
ctx.app.emit('error', err, ctx);
}
});

与Express对比:

特性ExpressKoa
异步处理回调/Promise原生async/await
错误处理手动传递自动冒泡
中间件模式直线型洋葱型
体积大小较大(内置功能多)轻量(核心功能)

面试官视角:

该题考察候选人对Koa架构的深度理解:

  • 要点清单: 理解洋葱模型概念;掌握async/await异步流程;了解koa-compose原理;能处理异步异常
  • 加分项: 了解与Express的区别;有Koa项目经验;理解Generator实现;能自定义compose函数
  • 常见失误: 不理解执行顺序;不了解异步等待;混淆与Express的区别;缺乏实践经验

延伸阅读:

什么是SSR,如何实现服务端渲染?

答案

核心概念

SSR(Server-Side Rendering)是指在服务器端将应用渲染成HTML字符串,然后发送给客户端。相比CSR(Client-Side Rendering),SSR能提供更好的首屏加载速度和SEO支持。

SSR优势

1. 性能优势

  • 更快的首屏加载时间(FCP)
  • 更好的用户体验,特别是慢网络环境
  • 减少客户端JavaScript执行负担

2. SEO优势

  • 搜索引擎可以直接抓取完整HTML
  • 更好的搜索排名和社交媒体分享
  • 支持无JavaScript环境的客户端

3. 可访问性

  • 内容在JavaScript加载前就可见
  • 更好的可访问性支持
  • 渐进增强的用户体验

实现方式:

// Server-Side Rendering (SSR) 演示
const http = require('http');
const fs = require('fs').promises;
const path = require('path');

/**
 * 1. 基础的HTML模板渲染
 */
class TemplateRenderer {
  constructor() {
    this.templates = new Map();
  }

  // 加载模板
  async loadTemplate(name, filePath) {
    try {
      const template = await fs.readFile(filePath, 'utf8');
      this.templates.set(name, template);
      return template;
    } catch (error) {
      console.error(`加载模板失败: ${filePath}`, error);
      throw error;
    }
  }

  // 渲染模板
  render(templateName, data = {}) {
    const template = this.templates.get(templateName);
    if (!template) {
      throw new Error(`模板不存在: ${templateName}`);
    }

    // 简单的模板替换
    return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
      return data[key] !== undefined ? String(data[key]) : '';
    });
  }

  // 支持条件渲染和循环的高级模板引擎
  renderAdvanced(templateName, data = {}) {
    let template = this.templates.get(templateName);
    if (!template) {
      throw new Error(`模板不存在: ${templateName}`);
    }

    // 处理条件渲染 {{#if condition}}...{{/if}}
    template = template.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, condition, content) => {
      return data[condition] ? content : '';
    });

    // 处理循环 {{#each items}}...{{/each}}
    template = template.replace(/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (match, arrayKey, itemTemplate) => {
      const array = data[arrayKey];
      if (!Array.isArray(array)) {
        return '';
      }

      return array.map(item => {
        return itemTemplate.replace(/\{\{(\w+)\}\}/g, (match, key) => {
          return item[key] !== undefined ? String(item[key]) : '';
        });
      }).join('');
    });

    // 处理普通变量替换
    template = template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
      return data[key] !== undefined ? String(data[key]) : '';
    });

    return template;
  }
}

/**
 * 2. React风格的虚拟DOM SSR演示
 */
class VirtualDOM {
  static createElement(tag, props = {}, ...children) {
    return {
      tag,
      props,
      children: children.flat()
    };
  }

  static renderToString(vdom) {
    if (typeof vdom === 'string' || typeof vdom === 'number') {
      return String(vdom);
    }

    if (!vdom || !vdom.tag) {
      return '';
    }

    const { tag, props = {}, children = [] } = vdom;
    
    // 构建属性字符串
    const propsString = Object.entries(props)
      .filter(([key, value]) => value !== null && value !== undefined)
      .map(([key, value]) => {
        // 处理特殊属性
        if (key === 'className') {
          key = 'class';
        }
        return `${key}="${String(value)}"`;
      })
      .join(' ');

    const propsAttr = propsString ? ` ${propsString}` : '';

    // 自闭合标签
    const voidTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
    if (voidTags.includes(tag)) {
      return `<${tag}${propsAttr} />`;
    }

    // 渲染子元素
    const childrenString = children
      .map(child => VirtualDOM.renderToString(child))
      .join('');

    return `<${tag}${propsAttr}>${childrenString}</${tag}>`;
  }
}

// 便捷函数
const h = VirtualDOM.createElement;

/**
 * 3. 组件化的SSR实现
 */
class Component {
  constructor(props = {}) {
    this.props = props;
    this.state = {};
  }

  render() {
    throw new Error('render method must be implemented');
  }

  static renderToString(ComponentClass, props = {}) {
    const instance = new ComponentClass(props);
    const vdom = instance.render();
    return VirtualDOM.renderToString(vdom);
  }
}

// 示例组件:用户卡片
class UserCard extends Component {
  render() {
    const { user } = this.props;
    
    return h('div', { className: 'user-card' },
      h('img', { 
        src: user.avatar || '/default-avatar.png', 
        alt: `${user.name}'s avatar`,
        className: 'user-avatar'
      }),
      h('div', { className: 'user-info' },
        h('h3', { className: 'user-name' }, user.name),
        h('p', { className: 'user-email' }, user.email),
        user.bio && h('p', { className: 'user-bio' }, user.bio)
      )
    );
  }
}

// 用户列表组件
class UserList extends Component {
  render() {
    const { users, title } = this.props;
    
    return h('div', { className: 'user-list' },
      h('h2', {}, title || '用户列表'),
      h('div', { className: 'users' },
        ...users.map(user => 
          Component.renderToString(UserCard, { user })
        )
      )
    );
  }
}

// 页面布局组件
class Layout extends Component {
  render() {
    const { title, children } = this.props;
    
    return h('html', { lang: 'zh-CN' },
      h('head', {},
        h('meta', { charset: 'utf-8' }),
        h('meta', { name: 'viewport', content: 'width=device-width, initial-scale=1' }),
        h('title', {}, title),
        h('style', {}, `
          body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
          .user-card { 
            border: 1px solid #ddd; 
            border-radius: 8px; 
            padding: 16px; 
            margin-bottom: 16px; 
            display: flex; 
            align-items: center; 
            gap: 12px;
          }
          .user-avatar { 
            width: 60px; 
            height: 60px; 
            border-radius: 50%; 
            object-fit: cover;
          }
          .user-name { margin: 0 0 8px 0; color: #333; }
          .user-email { margin: 0 0 4px 0; color: #666; font-size: 14px; }
          .user-bio { margin: 0; color: #888; font-size: 12px; }
          .header { background: #f5f5f5; padding: 20px; margin-bottom: 20px; }
          .nav { display: flex; gap: 20px; }
          .nav a { text-decoration: none; color: #0066cc; }
          .nav a:hover { text-decoration: underline; }
        `)
      ),
      h('body', {},
        h('div', { className: 'header' },
          h('h1', {}, 'SSR 演示应用'),
          h('nav', { className: 'nav' },
            h('a', { href: '/' }, '首页'),
            h('a', { href: '/users' }, '用户列表'),
            h('a', { href: '/about' }, '关于')
          )
        ),
        h('main', {}, children)
      )
    );
  }
}

/**
 * 4. 路由系统
 */
class Router {
  constructor() {
    this.routes = new Map();
  }

  route(path, handler) {
    this.routes.set(path, handler);
    return this;
  }

  async handle(request, response) {
    const url = new URL(request.url, 'http://localhost');
    const path = url.pathname;
    
    const handler = this.routes.get(path);
    if (handler) {
      try {
        await handler(request, response, url);
      } catch (error) {
        console.error('路由处理错误:', error);
        response.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
        response.end('<h1>500 - 服务器内部错误</h1>');
      }
    } else {
      response.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
      response.end('<h1>404 - 页面不存在</h1>');
    }
  }
}

/**
 * 5. 数据获取模拟
 */
class DataService {
  static async getUsers() {
    // 模拟异步数据获取
    await new Promise(resolve => setTimeout(resolve, 100));
    
    return [
      {
        id: 1,
        name: '张三',
        email: 'zhangsan@example.com',
        avatar: 'https://via.placeholder.com/60',
        bio: '全栈开发工程师,热爱技术分享'
      },
      {
        id: 2,
        name: '李四',
        email: 'lisi@example.com',
        avatar: 'https://via.placeholder.com/60',
        bio: 'Frontend专家,React和Vue都很熟练'
      },
      {
        id: 3,
        name: '王五',
        email: 'wangwu@example.com',
        avatar: 'https://via.placeholder.com/60',
        bio: '后端架构师,专注于高并发系统设计'
      }
    ];
  }

  static async getUserById(id) {
    const users = await this.getUsers();
    return users.find(user => user.id === parseInt(id));
  }
}

/**
 * 6. SSR应用主类
 */
class SSRApp {
  constructor() {
    this.router = new Router();
    this.templateRenderer = new TemplateRenderer();
    
    this.setupRoutes();
  }

  setupRoutes() {
    // 首页路由
    this.router.route('/', async (req, res) => {
      const html = Component.renderToString(Layout, {
        title: 'SSR演示 - 首页',
        children: h('div', {},
          h('h2', {}, '欢迎来到SSR演示应用'),
          h('p', {}, '这是一个使用Node.js实现的服务端渲染演示。'),
          h('ul', {},
            h('li', {}, '⚡ 快速的首屏加载'),
            h('li', {}, '🔍 SEO友好'),
            h('li', {}, '♿ 更好的可访问性'),
            h('li', {}, '📱 支持无JavaScript的客户端')
          ),
          h('a', { 
            href: '/users',
            style: 'display: inline-block; background: #0066cc; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; margin-top: 20px;'
          }, '查看用户列表')
        )
      });

      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
      res.end('<!DOCTYPE html>' + html);
    });

    // 用户列表路由
    this.router.route('/users', async (req, res) => {
      try {
        // 服务端数据获取
        const users = await DataService.getUsers();
        
        const userListHtml = Component.renderToString(UserList, {
          users,
          title: '用户列表'
        });

        const html = Component.renderToString(Layout, {
          title: 'SSR演示 - 用户列表',
          children: userListHtml
        });

        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        res.end('<!DOCTYPE html>' + html);
      } catch (error) {
        console.error('获取用户数据失败:', error);
        res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
        res.end('<h1>数据加载失败</h1>');
      }
    });

    // 关于页面路由
    this.router.route('/about', async (req, res) => {
      const html = Component.renderToString(Layout, {
        title: 'SSR演示 - 关于',
        children: h('div', {},
          h('h2', {}, '关于本演示'),
          h('p', {}, '这是一个Node.js服务端渲染(SSR)的完整示例,展示了:'),
          h('ol', {},
            h('li', {}, '虚拟DOM的服务端渲染'),
            h('li', {}, '组件化的页面结构'),
            h('li', {}, '路由系统'),
            h('li', {}, '数据获取和注入'),
            h('li', {}, 'SEO优化的HTML输出')
          ),
          h('h3', {}, '技术特性'),
          h('ul', {},
            h('li', {}, '🚀 零客户端JavaScript依赖'),
            h('li', {}, '📦 轻量级虚拟DOM实现'),
            h('li', {}, '🎨 CSS-in-JS样式支持'),
            h('li', {}, '🔧 模块化的组件系统')
          )
        )
      });

      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
      res.end('<!DOCTYPE html>' + html);
    });

    // API路由 - 返回JSON数据
    this.router.route('/api/users', async (req, res) => {
      try {
        const users = await DataService.getUsers();
        res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
        res.end(JSON.stringify(users, null, 2));
      } catch (error) {
        res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
        res.end(JSON.stringify({ error: 'Internal Server Error' }));
      }
    });
  }

  // 启动服务器
  listen(port = 3000) {
    const server = http.createServer((req, res) => {
      console.log(`${req.method} ${req.url}`);
      this.router.handle(req, res);
    });

    server.listen(port, () => {
      console.log(`🚀 SSR服务器启动成功!`);
      console.log(`📡 访问地址: http://localhost:${port}`);
      console.log(`\n📋 可用路由:`);
      console.log(`  🏠 http://localhost:${port}/`);
      console.log(`  👥 http://localhost:${port}/users`);
      console.log(`  ℹ️  http://localhost:${port}/about`);
      console.log(`  📊 http://localhost:${port}/api/users`);
      console.log(`\n💡 这些页面都是在服务端渲染的,查看源代码可以看到完整的HTML!`);
    });

    return server;
  }
}

/**
 * 7. 性能监控中间件
 */
function performanceMiddleware() {
  return (req, res, next) => {
    const start = process.hrtime.bigint();
    
    // 重写res.end方法来记录响应时间
    const originalEnd = res.end;
    res.end = function(...args) {
      const duration = process.hrtime.bigint() - start;
      const ms = Number(duration) / 1000000; // 转换为毫秒
      
      console.log(`⏱️  ${req.method} ${req.url} - ${res.statusCode} - ${ms.toFixed(2)}ms`);
      
      return originalEnd.apply(this, args);
    };
    
    if (next) next();
  };
}

/**
 * 8. 演示不同渲染模式的对比
 */
function demonstrateRenderingComparison() {
  console.log('=== 渲染模式对比演示 ===\n');

  const userData = [
    { id: 1, name: '张三', email: 'zhangsan@example.com' },
    { id: 2, name: '李四', email: 'lisi@example.com' }
  ];

  // 1. 客户端渲染(CSR)模拟
  console.log('1. 客户端渲染(CSR)输出:');
  const csrHtml = `
<!DOCTYPE html>
<html>
<head>
    <title>用户列表 - CSR</title>
</head>
<body>
    <div id="root">Loading...</div>
    <script>
        // 客户端获取数据并渲染
        fetch('/api/users')
            .then(res => res.json())
            .then(users => {
                const html = users.map(user => 
                    \`<div class="user">\${user.name} - \${user.email}</div>\`
                ).join('');
                document.getElementById('root').innerHTML = html;
            });
    </script>
</body>
</html>`;
  console.log(csrHtml.trim());

  // 2. 服务端渲染(SSR)输出
  console.log('\n2. 服务端渲染(SSR)输出:');
  const ssrUserList = userData.map(user => 
    `<div class="user">${user.name} - ${user.email}</div>`
  ).join('');
  
  const ssrHtml = `
<!DOCTYPE html>
<html>
<head>
    <title>用户列表 - SSR</title>
</head>
<body>
    <div id="root">
        <h1>用户列表</h1>
        ${ssrUserList}
    </div>
    <script>
        // 可选:客户端激活(hydration)
        console.log('页面已经渲染完成,可以添加交互功能');
    </script>
</body>
</html>`;
  console.log(ssrHtml.trim());

  // 3. 静态生成(SSG)说明
  console.log('\n3. 静态生成(SSG):');
  console.log('在构建时预先生成HTML文件,类似于SSR但在构建阶段完成');
  console.log('优点: 更快的响应速度,可以使用CDN缓存');
  console.log('缺点: 动态数据需要额外处理\n');

  // 4. 渲染性能对比
  console.log('4. 渲染模式性能对比:');
  const comparison = [
    ['指标', 'CSR', 'SSR', 'SSG'],
    ['首屏加载', '慢(需要JS)', '快(立即显示)', '最快(预生成)'],
    ['SEO友好', '差(需爬虫支持)', '好(完整HTML)', '最好(静态HTML)'],
    ['服务器负载', '低', '高', '最低'],
    ['实时数据', '好', '好', '需要额外处理'],
    ['开发复杂度', '低', '中', '中']
  ];

  console.table(comparison.slice(1), comparison[0]);
}

// 主演示函数
function main() {
  console.log('Node.js SSR (服务端渲染) 演示');
  console.log('================================\n');

  // 1. 基础演示
  demonstrateRenderingComparison();

  // 2. 启动SSR应用
  console.log('启动SSR演示应用...\n');
  const app = new SSRApp();
  const server = app.listen(3000);

  // 30秒后自动关闭
  setTimeout(() => {
    console.log('\n🔄 演示结束,关闭服务器');
    server.close();
  }, 30000);

  return server;
}

// 如果直接运行此文件
if (require.main === module) {
  main();
}

module.exports = {
  TemplateRenderer,
  VirtualDOM,
  Component,
  UserCard,
  UserList,
  Layout,
  Router,
  DataService,
  SSRApp,
  performanceMiddleware,
  demonstrateRenderingComparison
};

现代SSR框架:

Next.js (React生态)

// pages/users.js
export async function getServerSideProps(context) {
const users = await fetchUsers();
return { props: { users } };
}

export default function Users({ users }) {
return (
<div>
{users.map(user => <div key={user.id}>{user.name}</div>)}
</div>
);
}

Nuxt.js (Vue生态)

// pages/users.vue
export default {
async asyncData() {
const users = await $http.$get('/api/users');
return { users };
}
}

技术挑战:

1. 数据获取时机

  • 服务端需要预先获取数据
  • 避免客户端二次请求
  • 处理数据获取失败的情况

2. 状态管理

  • 服务端状态需要序列化传递给客户端
  • 避免客户端与服务端状态不一致
  • 处理敏感数据的过滤

3. 性能优化

  • 服务端渲染缓存策略
  • 代码分割和懒加载
  • 静态资源的预加载

面试官视角

该题考察候选人对现代Web架构的理解:

  • 要点清单: 理解SSR概念和优势;掌握基本实现方法;了解与CSR的区别;知道相关框架
  • 加分项: 有SSR项目经验;了解性能优化;掌握同构应用开发;理解SEO和可访问性
  • 常见失误: 只知道概念不了解实现;不理解技术权衡;缺乏实际经验;混淆不同渲染模式

延伸阅读