Skip to content
On this page

hono-etag

简介

hono-etag 是 Hono 框架的 ETag 中间件,它为 HTTP 响应自动生成和处理 ETag 头,帮助实现高效的缓存控制。ETag(Entity Tag)是 Web 缓存验证机制的一部分,允许客户端检查其缓存的资源是否仍然有效,从而减少不必要的数据传输。

安装

bash
npm install hono

基本使用

typescript
import { Hono } from 'hono'
import { etag } from 'hono/etag'

const app = new Hono()

// 应用 ETag 中间件到所有路由
app.use('*', etag())

// 或者只应用到特定路由
app.use('/api/*', etag())

app.get('/', (c) => {
  return c.text('Hello World')
})

app.get('/api/users', (c) => {
  const users = [
    { id: 1, name: '张三' },
    { id: 2, name: '李四' },
  ]
  return c.json(users)
})

app.listen(3000)

高级配置

typescript
import { Hono } from 'hono'
import { etag } from 'hono/etag'

const app = new Hono()

// 自定义 ETag 配置
app.use(
  '/api/*',
  etag({
    // 是否使用弱验证器(以 W/ 开头的 ETag)
    weak: true,

    // 自定义 ETag 生成函数
    generator: (body) => {
      // 基于响应体内容生成自定义 ETag
      // 这里使用简单的长度和内容的哈希组合
      if (!body) return ''

      const str = typeof body === 'string' ? body : JSON.stringify(body)
      let hash = 0
      for (let i = 0; i < str.length; i++) {
        hash = (hash << 5) - hash + str.charCodeAt(i)
        hash = hash & hash // 转换为 32 位整数
      }

      return `${str.length.toString(16)}-${hash.toString(16)}`
    },
  }),
)

// 结合其他中间件使用
app.use('/static/*', etag(), async (c, next) => {
  // 在 ETag 中间件之后添加缓存控制头
  await next()
  c.res.headers.set('Cache-Control', 'public, max-age=86400')
})

// 处理静态文件
app.get('/static/:filename', async (c) => {
  const filename = c.req.param('filename')
  // 这里应该是从文件系统读取文件的逻辑
  const content = await readFileContent(filename)
  return c.body(content)
})

// 处理 API 响应
app.get('/api/data', (c) => {
  const data = {
    id: 1,
    name: '示例数据',
    timestamp: new Date().toISOString(),
  }
  return c.json(data)
})

app.listen(3000)

// 模拟文件读取函数
async function readFileContent(filename: string): Promise<string> {
  // 在实际应用中,这里会从文件系统读取文件
  const files: Record<string, string> = {
    'example.txt': '这是一个示例文本文件的内容',
    'data.json': '{"name":"示例JSON","value":123}',
  }

  return files[filename] || '文件不存在'
}

与条件请求结合

typescript
import { Hono } from 'hono'
import { etag } from 'hono/etag'

const app = new Hono()

// 应用 ETag 中间件
app.use('*', etag())

// 处理可能包含条件请求的路由
app.get('/api/resources/:id', async (c) => {
  const id = c.req.param('id')

  // 获取资源(在实际应用中,这可能是从数据库获取)
  const resource = await getResourceById(id)

  if (!resource) {
    return c.json({ error: '资源不存在' }, 404)
  }

  // ETag 中间件会自动处理条件请求
  // 如果客户端发送了 If-None-Match 头,并且 ETag 匹配,
  // 中间件会自动返回 304 Not Modified 响应
  return c.json(resource)
})

app.listen(3000)

// 模拟资源获取函数
async function getResourceById(id: string) {
  const resources: Record<string, any> = {
    '1': { id: 1, name: '资源 1', data: '内容...' },
    '2': { id: 2, name: '资源 2', data: '内容...' },
  }

  return resources[id]
}

与缓存中间件结合

typescript
import { Hono } from 'hono'
import { etag } from 'hono/etag'
import { cache } from '@hono/cache'

const app = new Hono()

// 组合使用 ETag 和缓存中间件
app.use(
  '/api/*',
  etag({ weak: true }),
  cache({
    cacheName: 'api-cache',
    cacheControl: 'max-age=60',
  }),
)

app.get('/api/data', async (c) => {
  // 这个处理函数只有在缓存未命中且客户端没有有效的 ETag 时才会执行
  console.log('获取数据...')

  const data = {
    items: [
      { id: 1, name: '项目 1' },
      { id: 2, name: '项目 2' },
    ],
    timestamp: new Date().toISOString(),
  }

  return c.json(data)
})

app.listen(3000)

要保持清醒 永远不抱有意外的幻想 凭空的期待最要命