feat(cache): store ContentType in FileEntry for cache hits
- Add ContentType field to FileEntry struct - Update Set method signature to accept contentType parameter - Use cached ContentType in static.go cache hit branches - Update all test files to use new Set signature This avoids redundant MIME type detection on cache hits, reducing lock contention in mimeutil.DetectContentType. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
23fdcf89ae
commit
3c96f12f74
24
internal/cache/cache_bench_test.go
vendored
24
internal/cache/cache_bench_test.go
vendored
@ -35,7 +35,7 @@ func BenchmarkFileCacheGet(b *testing.B) {
|
|||||||
for i := range size {
|
for i := range size {
|
||||||
path := fmt.Sprintf("/file%d.txt", i)
|
path := fmt.Sprintf("/file%d.txt", i)
|
||||||
data := []byte("cached data content")
|
data := []byte("cached data content")
|
||||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
@ -65,14 +65,14 @@ func BenchmarkFileCacheSet(b *testing.B) {
|
|||||||
for i := range size {
|
for i := range size {
|
||||||
path := fmt.Sprintf("/file%d.txt", i)
|
path := fmt.Sprintf("/file%d.txt", i)
|
||||||
data := []byte("cached data content")
|
data := []byte("cached data content")
|
||||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
path := fmt.Sprintf("/newfile%d.txt", i)
|
path := fmt.Sprintf("/newfile%d.txt", i)
|
||||||
data := []byte("new cached data content")
|
data := []byte("new cached data content")
|
||||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ func BenchmarkFileCacheSet_Pooled(b *testing.B) {
|
|||||||
for i := range size {
|
for i := range size {
|
||||||
path := fmt.Sprintf("/file%d.txt", i)
|
path := fmt.Sprintf("/file%d.txt", i)
|
||||||
data := []byte("cached data content")
|
data := []byte("cached data content")
|
||||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@ -99,7 +99,7 @@ func BenchmarkFileCacheSet_Pooled(b *testing.B) {
|
|||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
path := fmt.Sprintf("/newfile%d.txt", i)
|
path := fmt.Sprintf("/newfile%d.txt", i)
|
||||||
data := []byte("new cached data content")
|
data := []byte("new cached data content")
|
||||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ func BenchmarkFileCacheSetNoEviction(b *testing.B) {
|
|||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
path := fmt.Sprintf("/file%d.txt", i)
|
path := fmt.Sprintf("/file%d.txt", i)
|
||||||
data := []byte("cached data content")
|
data := []byte("cached data content")
|
||||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ func BenchmarkFileCacheConcurrent(b *testing.B) {
|
|||||||
for i := range size {
|
for i := range size {
|
||||||
path := fmt.Sprintf("/file%d.txt", i)
|
path := fmt.Sprintf("/file%d.txt", i)
|
||||||
data := []byte("cached data content")
|
data := []byte("cached data content")
|
||||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
@ -142,7 +142,7 @@ func BenchmarkFileCacheConcurrent(b *testing.B) {
|
|||||||
if i%10 == 0 {
|
if i%10 == 0 {
|
||||||
path := fmt.Sprintf("/newfile%d.txt", i)
|
path := fmt.Sprintf("/newfile%d.txt", i)
|
||||||
data := []byte("updated data content")
|
data := []byte("updated data content")
|
||||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
} else {
|
} else {
|
||||||
path := fmt.Sprintf("/file%d.txt", i%size)
|
path := fmt.Sprintf("/file%d.txt", i%size)
|
||||||
fc.Get(path)
|
fc.Get(path)
|
||||||
@ -163,7 +163,7 @@ func BenchmarkFileCacheGetOnly(b *testing.B) {
|
|||||||
for i := range 1000 {
|
for i := range 1000 {
|
||||||
path := fmt.Sprintf("/static/file%d.css", i)
|
path := fmt.Sprintf("/static/file%d.css", i)
|
||||||
data := make([]byte, 1024) // 1KB 数据
|
data := make([]byte, 1024) // 1KB 数据
|
||||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
@ -188,14 +188,14 @@ func BenchmarkFileCacheSizeEviction(b *testing.B) {
|
|||||||
data := make([]byte, 1024) // 1KB 每条
|
data := make([]byte, 1024) // 1KB 每条
|
||||||
for i := range 1000 {
|
for i := range 1000 {
|
||||||
path := fmt.Sprintf("/file%d.txt", i)
|
path := fmt.Sprintf("/file%d.txt", i)
|
||||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
path := fmt.Sprintf("/newfile%d.txt", i)
|
path := fmt.Sprintf("/newfile%d.txt", i)
|
||||||
newData := make([]byte, 1024)
|
newData := make([]byte, 1024)
|
||||||
_ = fc.Set(path, newData, int64(len(newData)), time.Now())
|
_ = fc.Set(path, newData, int64(len(newData)), time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +208,7 @@ func BenchmarkFileCacheLRUTouch(b *testing.B) {
|
|||||||
for i := range 100 {
|
for i := range 100 {
|
||||||
path := fmt.Sprintf("/file%d.txt", i)
|
path := fmt.Sprintf("/file%d.txt", i)
|
||||||
data := []byte("cached data")
|
data := []byte("cached data")
|
||||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|||||||
30
internal/cache/cache_test.go
vendored
30
internal/cache/cache_test.go
vendored
@ -36,7 +36,7 @@ func TestFileCacheSetGet(t *testing.T) {
|
|||||||
path := "/test/file.txt"
|
path := "/test/file.txt"
|
||||||
data := []byte("Hello, World!")
|
data := []byte("Hello, World!")
|
||||||
|
|
||||||
err := fc.Set(path, data, int64(len(data)), time.Now())
|
err := fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Set() error: %v", err)
|
t.Errorf("Set() error: %v", err)
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@ func TestFileCacheSetGet(t *testing.T) {
|
|||||||
func TestFileCacheDelete(t *testing.T) {
|
func TestFileCacheDelete(t *testing.T) {
|
||||||
fc := NewFileCache(10, 1024, 1*time.Hour)
|
fc := NewFileCache(10, 1024, 1*time.Hour)
|
||||||
|
|
||||||
_ = fc.Set("/test.txt", []byte("data"), 4, time.Now())
|
_ = fc.Set("/test.txt", []byte("data"), 4, time.Now(), "text/plain")
|
||||||
|
|
||||||
fc.Delete("/test.txt")
|
fc.Delete("/test.txt")
|
||||||
|
|
||||||
@ -67,12 +67,12 @@ func TestFileCacheLRUEviction(t *testing.T) {
|
|||||||
// 最大 3 个条目
|
// 最大 3 个条目
|
||||||
fc := NewFileCache(3, 0, 1*time.Hour)
|
fc := NewFileCache(3, 0, 1*time.Hour)
|
||||||
|
|
||||||
_ = fc.Set("/a", []byte("a"), 1, time.Now())
|
_ = fc.Set("/a", []byte("a"), 1, time.Now(), "text/plain")
|
||||||
_ = fc.Set("/b", []byte("b"), 1, time.Now())
|
_ = fc.Set("/b", []byte("b"), 1, time.Now(), "text/plain")
|
||||||
_ = fc.Set("/c", []byte("c"), 1, time.Now())
|
_ = fc.Set("/c", []byte("c"), 1, time.Now(), "text/plain")
|
||||||
|
|
||||||
// 再添加一个,应该淘汰 /a
|
// 再添加一个,应该淘汰 /a
|
||||||
_ = fc.Set("/d", []byte("d"), 1, time.Now())
|
_ = fc.Set("/d", []byte("d"), 1, time.Now(), "text/plain")
|
||||||
|
|
||||||
_, ok := fc.Get("/a")
|
_, ok := fc.Get("/a")
|
||||||
if ok {
|
if ok {
|
||||||
@ -216,11 +216,11 @@ func TestFileCacheSizeEviction(t *testing.T) {
|
|||||||
// 最大 10 字节
|
// 最大 10 字节
|
||||||
fc := NewFileCache(0, 10, 1*time.Hour)
|
fc := NewFileCache(0, 10, 1*time.Hour)
|
||||||
|
|
||||||
_ = fc.Set("/a", []byte("12345"), 5, time.Now())
|
_ = fc.Set("/a", []byte("12345"), 5, time.Now(), "text/plain")
|
||||||
_ = fc.Set("/b", []byte("12345"), 5, time.Now())
|
_ = fc.Set("/b", []byte("12345"), 5, time.Now(), "text/plain")
|
||||||
|
|
||||||
// 再添加 6 字节,应该淘汰一个
|
// 再添加 6 字节,应该淘汰一个
|
||||||
_ = fc.Set("/c", []byte("123456"), 6, time.Now())
|
_ = fc.Set("/c", []byte("123456"), 6, time.Now(), "text/plain")
|
||||||
|
|
||||||
stats := fc.Stats()
|
stats := fc.Stats()
|
||||||
if stats.Size > 10 {
|
if stats.Size > 10 {
|
||||||
@ -231,7 +231,7 @@ func TestFileCacheSizeEviction(t *testing.T) {
|
|||||||
func TestFileCacheInactiveEviction(t *testing.T) {
|
func TestFileCacheInactiveEviction(t *testing.T) {
|
||||||
fc := NewFileCache(10, 1024, 100*time.Millisecond)
|
fc := NewFileCache(10, 1024, 100*time.Millisecond)
|
||||||
|
|
||||||
_ = fc.Set("/test", []byte("data"), 4, time.Now())
|
_ = fc.Set("/test", []byte("data"), 4, time.Now(), "text/plain")
|
||||||
|
|
||||||
// 立即获取应该成功
|
// 立即获取应该成功
|
||||||
_, ok := fc.Get("/test")
|
_, ok := fc.Get("/test")
|
||||||
@ -252,8 +252,8 @@ func TestFileCacheInactiveEviction(t *testing.T) {
|
|||||||
func TestFileCacheClear(t *testing.T) {
|
func TestFileCacheClear(t *testing.T) {
|
||||||
fc := NewFileCache(10, 1024, 1*time.Hour)
|
fc := NewFileCache(10, 1024, 1*time.Hour)
|
||||||
|
|
||||||
_ = fc.Set("/a", []byte("a"), 1, time.Now())
|
_ = fc.Set("/a", []byte("a"), 1, time.Now(), "text/plain")
|
||||||
_ = fc.Set("/b", []byte("b"), 1, time.Now())
|
_ = fc.Set("/b", []byte("b"), 1, time.Now(), "text/plain")
|
||||||
|
|
||||||
fc.Clear()
|
fc.Clear()
|
||||||
|
|
||||||
@ -266,8 +266,8 @@ func TestFileCacheClear(t *testing.T) {
|
|||||||
func TestFileCacheStats(t *testing.T) {
|
func TestFileCacheStats(t *testing.T) {
|
||||||
fc := NewFileCache(100, 1024, 1*time.Hour)
|
fc := NewFileCache(100, 1024, 1*time.Hour)
|
||||||
|
|
||||||
_ = fc.Set("/a", []byte("12345"), 5, time.Now())
|
_ = fc.Set("/a", []byte("12345"), 5, time.Now(), "text/plain")
|
||||||
_ = fc.Set("/b", []byte("12345"), 5, time.Now())
|
_ = fc.Set("/b", []byte("12345"), 5, time.Now(), "text/plain")
|
||||||
|
|
||||||
stats := fc.Stats()
|
stats := fc.Stats()
|
||||||
if stats.Entries != 2 {
|
if stats.Entries != 2 {
|
||||||
@ -569,7 +569,7 @@ func TestFileCacheRefreshCachedAt(t *testing.T) {
|
|||||||
data := []byte("test data")
|
data := []byte("test data")
|
||||||
|
|
||||||
// 设置缓存
|
// 设置缓存
|
||||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
|
||||||
|
|
||||||
// 获取原始 CachedAt 时间
|
// 获取原始 CachedAt 时间
|
||||||
entry, ok := fc.Get(path)
|
entry, ok := fc.Get(path)
|
||||||
|
|||||||
23
internal/cache/file_cache.go
vendored
23
internal/cache/file_cache.go
vendored
@ -29,14 +29,15 @@ import (
|
|||||||
|
|
||||||
// FileEntry 文件缓存条目,存储单个文件的缓存信息。
|
// FileEntry 文件缓存条目,存储单个文件的缓存信息。
|
||||||
type FileEntry struct {
|
type FileEntry struct {
|
||||||
ModTime time.Time
|
ModTime time.Time
|
||||||
CachedAt time.Time // 缓存时间,用于 TTL 验证(新鲜度)
|
CachedAt time.Time // 缓存时间,用于 TTL 验证(新鲜度)
|
||||||
LastAccess time.Time
|
LastAccess time.Time
|
||||||
element *list.Element
|
element *list.Element
|
||||||
Path string
|
Path string
|
||||||
Data []byte
|
Data []byte
|
||||||
Size int64
|
Size int64
|
||||||
ETag string // 预计算的 ETag,避免每次请求重新计算
|
ETag string // 预计算的 ETag,避免每次请求重新计算
|
||||||
|
ContentType string // 预计算的 MIME 类型,避免每次请求重新检测
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateETag 基于 ModTime 和 Size 生成 ETag。
|
// generateETag 基于 ModTime 和 Size 生成 ETag。
|
||||||
@ -173,10 +174,11 @@ func (c *FileCache) Get(path string) (*FileEntry, bool) {
|
|||||||
// - data: 文件内容字节
|
// - data: 文件内容字节
|
||||||
// - size: 文件大小(字节)
|
// - size: 文件大小(字节)
|
||||||
// - modTime: 文件最后修改时间
|
// - modTime: 文件最后修改时间
|
||||||
|
// - contentType: MIME 类型
|
||||||
//
|
//
|
||||||
// 返回值:
|
// 返回值:
|
||||||
// - error: 当前实现始终返回 nil
|
// - error: 当前实现始终返回 nil
|
||||||
func (c *FileCache) Set(path string, data []byte, size int64, modTime time.Time) error {
|
func (c *FileCache) Set(path string, data []byte, size int64, modTime time.Time, contentType string) error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
@ -190,6 +192,7 @@ func (c *FileCache) Set(path string, data []byte, size int64, modTime time.Time)
|
|||||||
entry.Size = size
|
entry.Size = size
|
||||||
entry.ModTime = modTime
|
entry.ModTime = modTime
|
||||||
entry.ETag = etag
|
entry.ETag = etag
|
||||||
|
entry.ContentType = contentType
|
||||||
entry.CachedAt = time.Now() // 更新缓存时间
|
entry.CachedAt = time.Now() // 更新缓存时间
|
||||||
entry.LastAccess = time.Now()
|
entry.LastAccess = time.Now()
|
||||||
c.currentSize += size
|
c.currentSize += size
|
||||||
@ -205,6 +208,7 @@ func (c *FileCache) Set(path string, data []byte, size int64, modTime time.Time)
|
|||||||
entry.Size = size
|
entry.Size = size
|
||||||
entry.ModTime = modTime
|
entry.ModTime = modTime
|
||||||
entry.ETag = etag
|
entry.ETag = etag
|
||||||
|
entry.ContentType = contentType
|
||||||
entry.CachedAt = time.Now()
|
entry.CachedAt = time.Now()
|
||||||
entry.LastAccess = time.Now()
|
entry.LastAccess = time.Now()
|
||||||
entry.element = c.lruList.PushFront(entry)
|
entry.element = c.lruList.PushFront(entry)
|
||||||
@ -266,6 +270,7 @@ func (c *FileCache) removeEntry(entry *FileEntry) {
|
|||||||
entry.CachedAt = time.Time{}
|
entry.CachedAt = time.Time{}
|
||||||
entry.LastAccess = time.Time{}
|
entry.LastAccess = time.Time{}
|
||||||
entry.ETag = ""
|
entry.ETag = ""
|
||||||
|
entry.ContentType = ""
|
||||||
entry.element = nil
|
entry.element = nil
|
||||||
c.entryPool.Put(entry)
|
c.entryPool.Put(entry)
|
||||||
}
|
}
|
||||||
|
|||||||
24
internal/cache/file_cache_allocation_test.go
vendored
24
internal/cache/file_cache_allocation_test.go
vendored
@ -37,7 +37,7 @@ func BenchmarkFileCacheSetAllocation_New(b *testing.B) {
|
|||||||
|
|
||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
path := fmt.Sprintf("/new/file%d.txt", i)
|
path := fmt.Sprintf("/new/file%d.txt", i)
|
||||||
fc.Set(path, data, size, time.Now())
|
fc.Set(path, data, size, time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ func BenchmarkFileCacheSetAllocation_Update(b *testing.B) {
|
|||||||
size := int64(len(data))
|
size := int64(len(data))
|
||||||
for i := range 1000 {
|
for i := range 1000 {
|
||||||
path := fmt.Sprintf("/update/file%d.txt", i)
|
path := fmt.Sprintf("/update/file%d.txt", i)
|
||||||
fc.Set(path, data, size, time.Now())
|
fc.Set(path, data, size, time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@ -61,7 +61,7 @@ func BenchmarkFileCacheSetAllocation_Update(b *testing.B) {
|
|||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
// 循环更新已有条目
|
// 循环更新已有条目
|
||||||
path := fmt.Sprintf("/update/file%d.txt", i%1000)
|
path := fmt.Sprintf("/update/file%d.txt", i%1000)
|
||||||
fc.Set(path, data, size, time.Now())
|
fc.Set(path, data, size, time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ func BenchmarkFileCacheSetAllocation_Eviction(b *testing.B) {
|
|||||||
size := int64(len(data))
|
size := int64(len(data))
|
||||||
for i := range 100 {
|
for i := range 100 {
|
||||||
path := fmt.Sprintf("/evict/file%d.txt", i)
|
path := fmt.Sprintf("/evict/file%d.txt", i)
|
||||||
fc.Set(path, data, size, time.Now())
|
fc.Set(path, data, size, time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@ -89,7 +89,7 @@ func BenchmarkFileCacheSetAllocation_Eviction(b *testing.B) {
|
|||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
// 每个 Set 都触发淘汰
|
// 每个 Set 都触发淘汰
|
||||||
path := fmt.Sprintf("/evict/new%d.txt", i)
|
path := fmt.Sprintf("/evict/new%d.txt", i)
|
||||||
fc.Set(path, data, size, time.Now())
|
fc.Set(path, data, size, time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ func BenchmarkFileCacheSetAllocation_EvictionWithPool(b *testing.B) {
|
|||||||
// 预填充
|
// 预填充
|
||||||
for i := range 100 {
|
for i := range 100 {
|
||||||
path := fmt.Sprintf("/pool/file%d.txt", i)
|
path := fmt.Sprintf("/pool/file%d.txt", i)
|
||||||
fc.Set(path, data, size, time.Now())
|
fc.Set(path, data, size, time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@ -113,7 +113,7 @@ func BenchmarkFileCacheSetAllocation_EvictionWithPool(b *testing.B) {
|
|||||||
|
|
||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
path := fmt.Sprintf("/pool/new%d.txt", i)
|
path := fmt.Sprintf("/pool/new%d.txt", i)
|
||||||
fc.Set(path, data, size, time.Now())
|
fc.Set(path, data, size, time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ func BenchmarkFileCacheSetAllocation_MemoryLimit(b *testing.B) {
|
|||||||
// 预填充到接近上限
|
// 预填充到接近上限
|
||||||
for i := range 900 {
|
for i := range 900 {
|
||||||
path := fmt.Sprintf("/mem/file%d.txt", i)
|
path := fmt.Sprintf("/mem/file%d.txt", i)
|
||||||
fc.Set(path, data, size, time.Now())
|
fc.Set(path, data, size, time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@ -138,7 +138,7 @@ func BenchmarkFileCacheSetAllocation_MemoryLimit(b *testing.B) {
|
|||||||
|
|
||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
path := fmt.Sprintf("/mem/new%d.txt", i)
|
path := fmt.Sprintf("/mem/new%d.txt", i)
|
||||||
fc.Set(path, data, size, time.Now())
|
fc.Set(path, data, size, time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ func BenchmarkFileCacheSetAllocation_Concurrent(b *testing.B) {
|
|||||||
i := 0
|
i := 0
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
path := fmt.Sprintf("/conc/file%d.txt", i)
|
path := fmt.Sprintf("/conc/file%d.txt", i)
|
||||||
fc.Set(path, data, size, time.Now())
|
fc.Set(path, data, size, time.Now(), "text/plain")
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -174,7 +174,7 @@ func BenchmarkFileCacheSetAllocation_ConcurrentEviction(b *testing.B) {
|
|||||||
// 预填充
|
// 预填充
|
||||||
for i := range 100 {
|
for i := range 100 {
|
||||||
path := fmt.Sprintf("/concevict/file%d.txt", i)
|
path := fmt.Sprintf("/concevict/file%d.txt", i)
|
||||||
fc.Set(path, data, size, time.Now())
|
fc.Set(path, data, size, time.Now(), "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@ -184,7 +184,7 @@ func BenchmarkFileCacheSetAllocation_ConcurrentEviction(b *testing.B) {
|
|||||||
i := 0
|
i := 0
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
path := fmt.Sprintf("/concevict/new%d.txt", i)
|
path := fmt.Sprintf("/concevict/new%d.txt", i)
|
||||||
fc.Set(path, data, size, time.Now())
|
fc.Set(path, data, size, time.Now(), "text/plain")
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -660,7 +660,7 @@ func (h *StaticHandler) handleStandard(ctx *fasthttp.RequestCtx, reqPath string)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Response.SetBody(entry.Data)
|
ctx.Response.SetBody(entry.Data)
|
||||||
ctx.Response.Header.SetContentType(mimeutil.DetectContentType(filePath))
|
ctx.Response.Header.SetContentType(entry.ContentType)
|
||||||
ctx.Response.Header.Set("ETag", entry.ETag)
|
ctx.Response.Header.Set("ETag", entry.ETag)
|
||||||
ctx.Response.Header.Set("Last-Modified", info.ModTime().UTC().Format(httpTimeFormat))
|
ctx.Response.Header.Set("Last-Modified", info.ModTime().UTC().Format(httpTimeFormat))
|
||||||
return
|
return
|
||||||
@ -681,7 +681,7 @@ func (h *StaticHandler) handleStandard(ctx *fasthttp.RequestCtx, reqPath string)
|
|||||||
h.fileCache.RefreshCachedAt(filePath)
|
h.fileCache.RefreshCachedAt(filePath)
|
||||||
}
|
}
|
||||||
ctx.Response.SetBody(entry.Data)
|
ctx.Response.SetBody(entry.Data)
|
||||||
ctx.Response.Header.SetContentType(mimeutil.DetectContentType(filePath))
|
ctx.Response.Header.SetContentType(entry.ContentType)
|
||||||
ctx.Response.Header.Set("ETag", entry.ETag)
|
ctx.Response.Header.Set("ETag", entry.ETag)
|
||||||
ctx.Response.Header.Set("Last-Modified", info.ModTime().UTC().Format(httpTimeFormat))
|
ctx.Response.Header.Set("Last-Modified", info.ModTime().UTC().Format(httpTimeFormat))
|
||||||
return
|
return
|
||||||
@ -734,9 +734,9 @@ func (h *StaticHandler) serveFile(ctx *fasthttp.RequestCtx, filePath string, inf
|
|||||||
// 检查文件是否被修改
|
// 检查文件是否被修改
|
||||||
if entry.ModTime.Equal(info.ModTime()) {
|
if entry.ModTime.Equal(info.ModTime()) {
|
||||||
// 缓存命中且文件未修改
|
// 缓存命中且文件未修改
|
||||||
// 使用缓存的 ETag,避免重新生成
|
// 使用缓存的 ETag 和 ContentType,避免重新生成
|
||||||
ctx.Response.SetBody(entry.Data)
|
ctx.Response.SetBody(entry.Data)
|
||||||
ctx.Response.Header.SetContentType(mimeutil.DetectContentType(filePath))
|
ctx.Response.Header.SetContentType(entry.ContentType)
|
||||||
ctx.Response.Header.Set("ETag", entry.ETag)
|
ctx.Response.Header.Set("ETag", entry.ETag)
|
||||||
ctx.Response.Header.Set("Last-Modified", info.ModTime().UTC().Format(httpTimeFormat))
|
ctx.Response.Header.Set("Last-Modified", info.ModTime().UTC().Format(httpTimeFormat))
|
||||||
h.setCacheHeaders(ctx)
|
h.setCacheHeaders(ctx)
|
||||||
@ -776,12 +776,13 @@ func (h *StaticHandler) serveFile(ctx *fasthttp.RequestCtx, filePath string, inf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 存入缓存(仅对小文件缓存)
|
// 存入缓存(仅对小文件缓存)
|
||||||
|
contentType := mimeutil.DetectContentType(filePath)
|
||||||
if h.fileCache != nil && info.Size() < 1024*1024 { // < 1MB
|
if h.fileCache != nil && info.Size() < 1024*1024 { // < 1MB
|
||||||
_ = h.fileCache.Set(filePath, data, info.Size(), info.ModTime())
|
_ = h.fileCache.Set(filePath, data, info.Size(), info.ModTime(), contentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Response.SetBody(data)
|
ctx.Response.SetBody(data)
|
||||||
ctx.Response.Header.SetContentType(mimeutil.DetectContentType(filePath))
|
ctx.Response.Header.SetContentType(contentType)
|
||||||
ctx.Response.Header.Set("ETag", etag)
|
ctx.Response.Header.Set("ETag", etag)
|
||||||
ctx.Response.Header.Set("Last-Modified", info.ModTime().UTC().Format(httpTimeFormat))
|
ctx.Response.Header.Set("Last-Modified", info.ModTime().UTC().Format(httpTimeFormat))
|
||||||
h.setCacheHeaders(ctx)
|
h.setCacheHeaders(ctx)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user