Chapter 03

请求与响应处理

深入 Context 对象,掌握从请求解析到流式响应的完整 I/O 处理能力

Context 对象(c)

每个处理函数都接收一个 Context 对象 c,它是 Hono 的核心接口,封装了当前请求的所有信息和响应构建方法:

// Context 的主要属性概览
app.get('/demo', async (c) => {
  // 请求相关
  c.req             // HonoRequest 对象
  c.req.raw         // 原始 Web API Request 对象
  c.req.method      // "GET" | "POST" | ...
  c.req.url         // 完整 URL 字符串
  c.req.path        // 路径部分: "/demo"

  // 环境相关(Cloudflare Workers 的 env/ctx 在这里)
  c.env             // 绑定的环境变量
  c.executionCtx    // 执行上下文(Workers 的 ctx)

  // 自定义变量(通过中间件传递数据)
  c.set('userId', '42')
  c.get('userId')  // → '42'

  return c.json({ ok: true })
})

请求信息提取

路径参数、查询参数、Header

app.get('/users/:id', async (c) => {
  // 路径参数
  const id = c.req.param('id')

  // 查询参数
  const format = c.req.query('format') ?? 'json'

  // 请求头
  const auth = c.req.header('Authorization')
  const contentType = c.req.header('Content-Type')

  return c.json({ id, format, auth })
})

解析请求体

// JSON body
app.post('/json', async (c) => {
  const body = await c.req.json<{ name: string }>()
  return c.json({ received: body.name })
})

// 文本 body
app.post('/text', async (c) => {
  const text = await c.req.text()
  return c.text(`Received: ${text}`)
})

// ArrayBuffer(用于二进制数据)
app.post('/binary', async (c) => {
  const buffer = await c.req.arrayBuffer()
  return c.json({ bytes: buffer.byteLength })
})

// FormData(表单提交)
app.post('/form', async (c) => {
  const form = await c.req.formData()
  const username = form.get('username') as string
  return c.json({ username })
})

响应构建

// JSON 响应(最常用)
c.json({ key: 'value' })         // 200
c.json({ error: 'Not found' }, 404)  // 自定义状态码
c.json(data, 201, { 'X-Custom': 'header' })  // 带额外 Header

// 纯文本
c.text('Hello World')
c.text('Created', 201)

// HTML
c.html('<h1>Hello</h1>')
c.html(`<!DOCTYPE html><html><body>${content}</body></html>`)

// 重定向
c.redirect('/login')        // 302
c.redirect('/new-url', 301) // 永久重定向

// 无内容响应
c.body(null, 204)  // 204 No Content

// 设置响应 Header(在返回响应前调用)
c.header('X-Powered-By', 'Hono')
c.header('Cache-Control', 'max-age=3600')
return c.json(data)

Cookie 操作

import { getCookie, setCookie, deleteCookie } from 'hono/cookie'

// 读取 Cookie
app.get('/profile', (c) => {
  const sessionId = getCookie(c, 'session_id')
  if (!sessionId) {
    return c.redirect('/login')
  }
  return c.json({ session: sessionId })
})

// 设置 Cookie
app.post('/login', async (c) => {
  // ... 验证逻辑
  setCookie(c, 'session_id', 'abc123', {
    httpOnly: true,    // 防止 JS 读取
    secure: true,      // 仅 HTTPS
    sameSite: 'Lax',  // CSRF 防护
    maxAge: 60 * 60 * 24,  // 1天,单位秒
    path: '/',
  })
  return c.json({ ok: true })
})

// 删除 Cookie(登出)
app.post('/logout', (c) => {
  deleteCookie(c, 'session_id')
  return c.redirect('/')
})

流式响应

基础流式传输

import { stream } from 'hono/streaming'

app.get('/stream', (c) => {
  return stream(c, async (s) => {
    for (let i = 0; i < 5; i++) {
      await s.write(`Chunk ${i}\n`)
      await s.sleep(200)  // 200ms 间隔
    }
  })
})

SSE(Server-Sent Events)

SSE 是一种服务器向客户端单向推送数据的协议,适合实时通知、AI 流式输出等场景:

import { streamSSE } from 'hono/streaming'

app.get('/sse/notifications', (c) => {
  return streamSSE(c, async (stream) => {
    let id = 0

    // 发送 SSE 事件
    await stream.writeSSE({
      data: JSON.stringify({ type: 'connected' }),
      event: 'connect',
      id: String(id++),
    })

    // 定期推送数据
    while (true) {
      if (stream.closed) break  // 客户端断开时退出

      await stream.writeSSE({
        data: JSON.stringify({
          time: new Date().toISOString(),
          value: Math.random(),
        }),
        event: 'update',
        id: String(id++),
      })

      await stream.sleep(1000)
    }
  })
})
// 前端 EventSource 接收 SSE
const es = new EventSource('/sse/notifications')

es.addEventListener('connect', (e) => {
  console.log('Connected:', JSON.parse(e.data))
})

es.addEventListener('update', (e) => {
  const data = JSON.parse(e.data)
  console.log('Update:', data)
})

SSE vs WebSocket:SSE 是单向的(服务器→客户端),基于 HTTP,更简单且天然支持重连。WebSocket 是双向的,需要升级协议。对于 AI 流式输出、实时通知等单向数据流场景,SSE 是更合适的选择。

文件上传处理

app.post('/upload', async (c) => {
  const formData = await c.req.formData()

  // 获取文件
  const file = formData.get('file') as File | null

  if (!file || !(file instanceof File)) {
    return c.json({ error: 'No file provided' }, 400)
  }

  // 读取文件内容
  const arrayBuffer = await file.arrayBuffer()
  const bytes = new Uint8Array(arrayBuffer)

  // 验证文件类型
  if (!file.type.startsWith('image/')) {
    return c.json({ error: 'Only images allowed' }, 415)
  }

  // 验证文件大小(最大 5MB)
  if (file.size > 5 * 1024 * 1024) {
    return c.json({ error: 'File too large' }, 413)
  }

  // 在 Cloudflare Workers 中可上传到 R2
  // await c.env.MY_BUCKET.put(file.name, bytes)

  return c.json({
    name: file.name,
    size: file.size,
    type: file.type,
  })
})

Raw Response

当 Hono 内置方法不够用时,可以直接返回原生 Response 对象:

// 返回图片(二进制数据)
app.get('/image', async (c) => {
  const imageData = await fetch('https://example.com/img.png')
  const buffer = await imageData.arrayBuffer()

  return new Response(buffer, {
    status: 200,
    headers: {
      'Content-Type': 'image/png',
      'Cache-Control': 'public, max-age=86400',
    },
  })
})

// 返回 CSV 文件下载
app.get('/export.csv', (c) => {
  const csv = `id,name,email\n1,Alice,alice@example.com`
  return new Response(csv, {
    headers: {
      'Content-Type': 'text/csv; charset=utf-8',
      'Content-Disposition': 'attachment; filename="export.csv"',
    },
  })
})

c.var 共享变量:在中间件中通过 c.set('key', value) 设置的数据,可以在后续中间件和处理函数中通过 c.get('key')c.var.key 读取。这是中间件向处理函数传递数据(如当前用户信息)的标准方式。