教程 hono tutorial

表单与文件上传

Hono 可以通过 Web 标准的 formData() 读取表单字段和上传文件。不同运行时对文件大小、临时存储和流式处理能力可能不同,生产环境要结合平台限制设计。

读取普通表单

ts
app.post('/contact', async (c) => {
  const form = await c.req.formData()
  const email = String(form.get('email') ?? '')
  const message = String(form.get('message') ?? '')

  if (!email || !message) {
    return c.json({ message: 'email and message are required' }, 400)
  }

  return c.json({
    received: true,
    email,
  })
})

HTML 表单:

html
<form action="/contact" method="post">
  <input name="email" type="email" />
  <textarea name="message"></textarea>
  <button type="submit">提交</button>
</form>

读取文件

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

  if (!(file instanceof File)) {
    return c.json({ message: 'file is required' }, 400)
  }

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

测试:

bash
curl -X POST http://localhost:3000/upload \
  -F "file=@./avatar.png"

保存文件

Node.js 中可以把文件转换为 ArrayBuffer 后写入磁盘:

ts
import { writeFile } from 'node:fs/promises'
import { join } from 'node:path'

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

  if (!(file instanceof File)) {
    return c.json({ message: 'file is required' }, 400)
  }

  const bytes = await file.arrayBuffer()
  const filename = `${crypto.randomUUID()}-${file.name}`
  const path = join(process.cwd(), 'uploads', filename)

  await writeFile(path, Buffer.from(bytes))

  return c.json({ filename })
})

Cloudflare Workers 中更常见的是写入 R2,而不是本地文件系统。

安全注意事项

  • 限制上传大小,避免内存被大文件耗尽。
  • 不要信任客户端传来的文件名。
  • 检查 MIME 类型和文件内容,特别是图片、压缩包和可执行文件。
  • 私有文件不要直接放在公开静态目录。
  • 对上传结果做鉴权,避免任意用户读取其他用户文件。

如果文件上传是核心功能,建议使用对象存储,并将上传、扫描、缩略图、访问控制拆成清晰流程。