Appearance
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)