Skip to content
On this page

Koa请求与响应

在Koa中,请求和响应处理是通过Context对象统一管理的。Context(ctx)对象是Koa的请求和响应对象的封装,提供了便捷的方法来处理HTTP请求和响应。

Context对象

Context对象是Koa的核心,它封装了Node.js的request和response对象:

javascript
app.use(async (ctx) => {
  // ctx.request - Koa Request对象
  // ctx.response - Koa Response对象
  // ctx.req - Node.js原生request对象
  // ctx.res - Node.js原生response对象
});

请求对象(Request)

请求基本信息

javascript
app.use(async (ctx) => {
  // HTTP方法
  console.log(ctx.request.method); // GET, POST, PUT, DELETE等
  
  // URL信息
  console.log(ctx.request.url);        // 完整URL路径和查询字符串
  console.log(ctx.request.path);       // 路径部分
  console.log(ctx.request.query);      // 解析后的查询参数对象
  console.log(ctx.request.querystring); // 原始查询字符串
  
  // 协议和主机信息
  console.log(ctx.request.origin);     // 协议+主机名
  console.log(ctx.request.href);       // 完整URL
  console.log(ctx.request.protocol);   // http或https
  console.log(ctx.request.host);       // 主机名+端口
  console.log(ctx.request.hostname);   // 仅主机名
  
  // 客户端信息
  console.log(ctx.request.ip);         // 客户端IP
  console.log(ctx.request.ips);        // 代理IP数组(如果配置了proxy)
});

请求头处理

javascript
app.use(async (ctx) => {
  // 获取所有请求头
  console.log(ctx.request.headers);
  
  // 获取特定请求头
  console.log(ctx.request.get('Content-Type'));
  console.log(ctx.request.get('User-Agent'));
  console.log(ctx.request.get('Authorization'));
  
  // 检查内容类型
  if (ctx.request.is('text/html')) {
    console.log('Content-Type is text/html');
  }
  
  if (ctx.request.is('json')) {
    console.log('Content-Type is JSON');
  }
  
  // 检查接受的内容类型
  const acceptedType = ctx.request.accepts(['html', 'json', 'text']);
  console.log('Client accepts:', acceptedType);
  
  // 检查接受的字符集
  console.log('Accepted charsets:', ctx.request.acceptsCharsets());
  
  // 检查接受的编码
  console.log('Accepted encodings:', ctx.request.acceptsEncodings());
  
  // 检查接受的语言
  console.log('Accepted languages:', ctx.request.acceptsLanguages());
});

请求体处理

javascript
const bodyParser = require('koa-bodyparser');

app.use(bodyParser());

app.use(async (ctx) => {
  if (ctx.method === 'POST') {
    // 解析后的请求体
    console.log(ctx.request.body);
    
    // 根据内容类型处理不同类型的数据
    if (ctx.request.is('application/json')) {
      // JSON数据
      const jsonData = ctx.request.body;
      console.log('JSON data:', jsonData);
    } else if (ctx.request.is('application/x-www-form-urlencoded')) {
      // 表单数据
      const formData = ctx.request.body;
      console.log('Form data:', formData);
    } else if (ctx.request.is('multipart/form-data')) {
      // 文件上传数据(需要其他中间件如koa-multer)
      console.log('File upload data');
    }
  }
});

请求参数处理

javascript
// 使用@koa/router处理路由参数
const Router = require('@koa/router');
const router = new Router();

router.get('/users/:id/posts/:postId', async (ctx) => {
  // 路由参数
  console.log(ctx.params.id);      // 从URL路径获取
  console.log(ctx.params.postId);  // 从URL路径获取
  
  // 查询参数
  console.log(ctx.query.page);     // 从URL查询字符串获取
  console.log(ctx.query.limit);
  
  // 获取原始查询字符串
  console.log(ctx.querystring);
  
  ctx.body = {
    userId: ctx.params.id,
    postId: ctx.params.postId,
    page: ctx.query.page,
    limit: ctx.query.limit
  };
});

响应对象(Response)

响应状态码

javascript
app.use(async (ctx) => {
  // 设置响应状态码
  ctx.status = 200;           // 成功
  ctx.status = 201;           // 创建成功
  ctx.status = 400;           // 请求错误
  ctx.status = 401;           // 未授权
  ctx.status = 403;           // 禁止访问
  ctx.status = 404;           // 未找到
  ctx.status = 500;           // 服务器错误
  
  // 或者通过response对象设置
  ctx.response.status = 200;
  
  // 使用状态消息
  ctx.message = 'Custom message';
});

响应体设置

javascript
app.use(async (ctx) => {
  // 设置不同类型的响应体
  
  // 字符串响应
  ctx.body = 'Hello World';
  
  // JSON响应
  ctx.body = {
    message: 'Success',
    data: [1, 2, 3],
    timestamp: new Date()
  };
  
  // 数组响应
  ctx.body = [1, 2, 3, 4];
  
  // Buffer响应
  ctx.body = Buffer.from('Hello World');
  
  // Stream响应
  const fs = require('fs');
  ctx.body = fs.createReadStream('file.txt');
  
  // 错误响应
  ctx.body = { error: 'Something went wrong' };
  
  // 空响应
  ctx.body = '';
});

响应头设置

javascript
app.use(async (ctx) => {
  // 设置单个响应头
  ctx.set('X-Custom-Header', 'value');
  
  // 设置多个响应头
  ctx.set({
    'X-API-Version': '1.0',
    'X-Response-Time': '100ms',
    'Cache-Control': 'no-cache'
  });
  
  // 或者通过response对象设置
  ctx.response.set('Content-Type', 'application/json');
  
  // 设置内容类型
  ctx.type = 'json';           // 自动设置Content-Type为application/json
  ctx.type = 'html';           // 自动设置Content-Type为text/html
  ctx.type = 'text/plain';     // 显式设置Content-Type
  
  // 设置内容长度
  ctx.length = Buffer.byteLength(ctx.body);
  
  // 设置ETag
  ctx.response.etag = '12345';
  
  // 设置最后修改时间
  ctx.response.lastModified = new Date();
});

响应重定向

javascript
app.use(async (ctx) => {
  // 内部重定向
  ctx.redirect('/new-path');
  
  // 外部重定向
  ctx.redirect('https://example.com');
  
  // 重定向回上一页
  ctx.redirect('back');
  
  // 或者通过response对象
  ctx.response.redirect('/new-path');
});

高级请求处理

文件上传处理

javascript
const multer = require('@koa/multer');
const upload = multer({ dest: 'uploads/' });

// 单文件上传
router.post('/upload', upload.single('file'), async (ctx) => {
  const file = ctx.file;
  ctx.body = {
    filename: file.filename,
    originalName: file.originalname,
    size: file.size
  };
});

// 多文件上传
router.post('/uploads', upload.array('files', 12), async (ctx) => {
  const files = ctx.files;
  ctx.body = {
    fileCount: files.length,
    files: files.map(file => ({
      filename: file.filename,
      originalName: file.originalname,
      size: file.size
    }))
  };
});

请求流处理

javascript
const fs = require('fs');

app.use(async (ctx) => {
  if (ctx.method === 'POST' && ctx.path === '/upload-stream') {
    // 处理请求流
    const writeStream = fs.createWriteStream('uploaded-file.txt');
    
    // 将请求流管道到文件
    ctx.req.pipe(writeStream);
    
    // 等待写入完成
    await new Promise((resolve, reject) => {
      writeStream.on('finish', resolve);
      writeStream.on('error', reject);
    });
    
    ctx.body = { message: 'File uploaded successfully' };
  }
});

高级响应处理

响应流

javascript
const fs = require('fs');

// 流式响应大文件
router.get('/large-file', async (ctx) => {
  ctx.set('Content-Type', 'application/octet-stream');
  ctx.set('Content-Disposition', 'attachment; filename="large-file.bin"');
  
  // 创建文件读取流
  ctx.body = fs.createReadStream('large-file.bin');
});

// JSON流式响应
router.get('/stream-json', async (ctx) => {
  ctx.set('Content-Type', 'application/json');
  ctx.body = JSON.stringify({ data: 'streaming' });
});

响应压缩

javascript
const compress = require('koa-compress');

app.use(compress({
  threshold: 2048,  // 大于2KB的响应才压缩
  gzip: {
    flush: require('zlib').constants.Z_SYNC_FLUSH
  },
  deflate: {
    flush: require('zlib').constants.Z_SYNC_FLUSH
  },
  br: false // 禁用Brotli压缩
}));

app.use(async (ctx) => {
  // 大响应体会自动被压缩
  ctx.body = new Array(10000).fill('Hello World').join('\n');
});

错误处理

请求验证错误

javascript
const validateRequest = async (ctx, next) => {
  // 验证内容类型
  if (ctx.method === 'POST' && !ctx.request.is('application/json')) {
    ctx.status = 400;
    ctx.body = { error: 'Content-Type must be application/json' };
    return;
  }
  
  // 验证请求体大小
  const contentLength = ctx.request.length;
  if (contentLength > 10 * 1024 * 1024) { // 10MB限制
    ctx.status = 413;
    ctx.body = { error: 'Request body too large' };
    return;
  }
  
  await next();
};

app.use(validateRequest);

响应错误处理

javascript
app.use(async (ctx, next) => {
  try {
    await next();
    
    // 如果没有设置响应体且状态码是404
    if (ctx.status === 404 && !ctx.body) {
      ctx.status = 404;
      ctx.body = { error: 'Not Found', path: ctx.path };
    }
  } catch (err) {
    // 统一错误处理
    ctx.status = err.status || 500;
    ctx.body = {
      error: err.expose ? err.message : 'Internal Server Error',
      status: ctx.status
    };
    
    // 记录错误
    console.error('Response error:', err);
  }
});

性能优化

响应缓存

javascript
// 简单的响应缓存
const responseCache = new Map();

const cacheResponse = (ttl = 300) => {
  return async (ctx, next) => {
    const cacheKey = `${ctx.method}:${ctx.path}`;
    const cached = responseCache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < ttl * 1000) {
      ctx.body = cached.body;
      ctx.status = cached.status;
      ctx.set(cached.headers);
      return;
    }
    
    await next();
    
    // 只缓存成功的GET请求
    if (ctx.method === 'GET' && ctx.status >= 200 && ctx.status < 300) {
      responseCache.set(cacheKey, {
        body: ctx.body,
        status: ctx.status,
        headers: { ...ctx.response.headers },
        timestamp: Date.now()
      });
    }
  };
};

router.get('/cached-data', cacheResponse(600), async (ctx) => {
  ctx.body = await getExpensiveData();
});

条件请求处理

javascript
// 处理条件请求(If-None-Match, If-Modified-Since)
const handleConditionalRequest = async (ctx, next) => {
  await next();
  
  // 生成ETag
  const etag = `"${Buffer.from(JSON.stringify(ctx.body)).toString('base64')}"`;
  
  // 检查条件请求头
  const ifNoneMatch = ctx.request.get('If-None-Match');
  const ifModifiedSince = ctx.request.get('If-Modified-Since');
  
  if (ifNoneMatch === etag) {
    ctx.status = 304; // Not Modified
    ctx.body = null;
    return;
  }
  
  ctx.set('ETag', etag);
};

通过掌握Koa的请求和响应处理,可以构建功能强大且高效的Web应用。