阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 多种内容类型的响应处理

多种内容类型的响应处理

作者:陈川 阅读数:6840人阅读 分类: Node.js

处理JSON响应

Koa2中处理JSON响应是最常见的场景之一。通过ctx.body直接返回JavaScript对象,Koa会自动将其转换为JSON格式并设置正确的Content-Type头。

router.get('/api/user', async (ctx) => {
  ctx.body = {
    id: 1,
    name: '张三',
    age: 28,
    hobbies: ['编程', '阅读', '旅行']
  };
});

当客户端请求这个路由时,会收到如下响应:

{
  "id": 1,
  "name": "张三",
  "age": 28,
  "hobbies": ["编程", "阅读", "旅行"]
}

对于更复杂的场景,可以使用koa-json中间件来自定义JSON格式:

const json = require('koa-json');

app.use(json({
  pretty: process.env.NODE_ENV !== 'production',
  param: 'pretty',
  spaces: 2
}));

处理HTML响应

返回HTML内容时,需要手动设置Content-Type为text/html。可以使用模板引擎如ejs、pug等,也可以直接返回HTML字符串。

router.get('/welcome', async (ctx) => {
  ctx.type = 'text/html';
  ctx.body = `
    <!DOCTYPE html>
    <html>
      <head>
        <title>欢迎页</title>
      </head>
      <body>
        <h1>欢迎来到我们的网站</h1>
        <p>当前时间: ${new Date().toLocaleString()}</p>
      </body>
    </html>
  `;
});

使用ejs模板引擎的示例:

const views = require('koa-views');

app.use(views(__dirname + '/views', {
  extension: 'ejs'
}));

router.get('/profile', async (ctx) => {
  await ctx.render('profile', {
    user: {
      name: '李四',
      avatar: '/images/avatar.jpg'
    }
  });
});

处理文件下载

对于文件下载响应,需要设置适当的Content-Disposition头。Koa提供了便捷的attachment方法。

const fs = require('fs');
const path = require('path');

router.get('/download', async (ctx) => {
  const filePath = path.join(__dirname, 'files/report.pdf');
  ctx.attachment('月度报告.pdf');
  ctx.type = 'application/pdf';
  ctx.body = fs.createReadStream(filePath);
});

如果要实现动态生成文件并下载:

router.get('/export-csv', async (ctx) => {
  const data = [
    ['姓名', '年龄', '城市'],
    ['张三', '28', '北京'],
    ['李四', '32', '上海']
  ];
  
  const csvContent = data.map(row => row.join(',')).join('\n');
  
  ctx.attachment('用户数据.csv');
  ctx.type = 'text/csv';
  ctx.body = csvContent;
});

处理流式响应

Koa原生支持流式响应,这对于大文件或实时数据非常有用。

const { PassThrough } = require('stream');

router.get('/stream', async (ctx) => {
  ctx.type = 'text/plain';
  const stream = new PassThrough();
  
  let count = 0;
  const timer = setInterval(() => {
    stream.write(`数据块 ${count++}\n`);
    if (count >= 10) {
      clearInterval(timer);
      stream.end();
    }
  }, 500);
  
  ctx.body = stream;
});

另一个实际例子是代理转发:

const axios = require('axios');

router.get('/proxy-image', async (ctx) => {
  const response = await axios.get('https://example.com/large-image.jpg', {
    responseType: 'stream'
  });
  
  ctx.type = response.headers['content-type'];
  ctx.body = response.data;
});

处理SSE(Server-Sent Events)

SSE允许服务器向客户端推送事件,适用于实时更新场景。

router.get('/sse', async (ctx) => {
  ctx.request.socket.setTimeout(0);
  ctx.req.socket.setNoDelay(true);
  ctx.req.socket.setKeepAlive(true);
  
  ctx.set({
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });
  
  const stream = new PassThrough();
  ctx.body = stream;
  
  let count = 0;
  const sendEvent = () => {
    stream.write(`event: update\n`);
    stream.write(`id: ${Date.now()}\n`);
    stream.write(`data: ${JSON.stringify({ count: count++, time: new Date() })}\n\n`);
  };
  
  const timer = setInterval(sendEvent, 1000);
  
  ctx.req.on('close', () => {
    clearInterval(timer);
    stream.end();
  });
});

处理GraphQL响应

在Koa中集成GraphQL服务需要额外的中间件。

const { graphqlHTTP } = require('koa-graphql');
const { buildSchema } = require('graphql');

const schema = buildSchema(`
  type Query {
    hello: String
    user(id: ID!): User
  }
  
  type User {
    id: ID!
    name: String
    email: String
  }
`);

const rootValue = {
  hello: () => 'Hello world!',
  user: ({ id }) => ({
    id,
    name: '用户' + id,
    email: `user${id}@example.com`
  })
};

router.all('/graphql', graphqlHTTP({
  schema,
  rootValue,
  graphiql: true
}));

处理WebSocket升级

虽然Koa本身不直接处理WebSocket,但可以配合其他库实现。

const Koa = require('koa');
const WebSocket = require('ws');

const app = new Koa();
const server = app.listen(3000);

const wss = new WebSocket.Server({ server });

wss.on('connection', (ws) => {
  ws.on('message', (message) => {
    console.log('收到消息:', message);
    ws.send(`服务器回复: ${message}`);
  });
  
  ws.send('连接已建立');
});

// Koa中间件
app.use(async (ctx) => {
  // 普通HTTP请求处理
  ctx.body = 'HTTP请求处理';
});

处理内容协商

根据客户端Accept头返回不同格式的响应。

router.get('/resource', async (ctx) => {
  const data = {
    id: 123,
    title: '内容协商示例',
    content: '这是一个演示不同响应格式的例子'
  };
  
  switch (ctx.accepts('json', 'html', 'xml', 'text')) {
    case 'json':
      ctx.body = data;
      break;
    case 'html':
      ctx.type = 'text/html';
      ctx.body = `
        <!DOCTYPE html>
        <html>
          <head><title>${data.title}</title></head>
          <body>
            <h1>${data.title}</h1>
            <p>${data.content}</p>
          </body>
        </html>
      `;
      break;
    case 'xml':
      ctx.type = 'application/xml';
      ctx.body = `
        <?xml version="1.0" encoding="UTF-8"?>
        <resource>
          <id>${data.id}</id>
          <title>${data.title}</title>
          <content>${data.content}</content>
        </resource>
      `;
      break;
    default:
      ctx.type = 'text/plain';
      ctx.body = `${data.title}\n\n${data.content}`;
  }
});

处理错误响应

统一的错误处理中间件可以规范化错误响应格式。

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.type = 'application/json';
    ctx.body = {
      error: {
        code: err.code || 'INTERNAL_ERROR',
        message: err.message,
        details: err.details,
        timestamp: new Date().toISOString()
      }
    };
    
    if (process.env.NODE_ENV === 'development') {
      ctx.body.error.stack = err.stack;
    }
    
    ctx.app.emit('error', err, ctx);
  }
});

// 使用示例
router.get('/protected', async (ctx) => {
  if (!ctx.headers.authorization) {
    const error = new Error('未授权访问');
    error.status = 401;
    error.code = 'UNAUTHORIZED';
    throw error;
  }
  
  ctx.body = { message: '欢迎访问受保护资源' };
});

处理重定向响应

Koa提供了便捷的重定向方法。

router.get('/old-route', async (ctx) => {
  ctx.redirect('/new-route');
  ctx.status = 301; // 永久重定向
});

router.get('/login', async (ctx) => {
  if (!ctx.session.user) {
    ctx.redirect('/auth?return_url=' + encodeURIComponent(ctx.url));
    return;
  }
  
  ctx.body = '欢迎回来';
});

// 带闪存消息的重定向
router.post('/comments', async (ctx) => {
  try {
    await createComment(ctx.request.body);
    ctx.flash = { type: 'success', message: '评论发布成功' };
    ctx.redirect('back');
  } catch (err) {
    ctx.flash = { type: 'error', message: err.message };
    ctx.redirect('back');
  }
});

处理自定义内容类型

对于非标准内容类型,可以手动设置Content-Type。

router.get('/ical', async (ctx) => {
  const icalContent = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN
BEGIN:VEVENT
UID:12345
DTSTAMP:20230501T120000Z
DTSTART:20230501T130000Z
DTEND:20230501T140000Z
SUMMARY:团队会议
END:VEVENT
END:VCALENDAR`;
  
  ctx.type = 'text/calendar';
  ctx.body = icalContent;
});

router.get('/rss', async (ctx) => {
  const rssContent = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>示例RSS</title>
    <description>这是一个示例RSS源</description>
    <item>
      <title>第一条新闻</title>
      <description>这是第一条新闻的内容</description>
    </item>
  </channel>
</rss>`;
  
  ctx.type = 'application/rss+xml';
  ctx.body = rssContent;
});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌