From 93e3ee0b14f46ebf46aeef3a967a1c26fa684393 Mon Sep 17 00:00:00 2001 From: xfy Date: Fri, 10 Apr 2026 16:55:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(handler,compression):=20=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=20mimeutil=20=E7=BB=9F=E4=B8=80=20Content-Type=20=E6=A3=80?= =?UTF-8?q?=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 替换 mime.TypeByExtension 为 mimeutil.DetectContentType - 为 sendfile 大文件路径添加 Content-Type 设置 - 为预压缩文件添加原始 Content-Type - 添加大文件 Content-Type 测试验证 Co-Authored-By: Claude Opus 4.6 --- internal/handler/static.go | 10 ++-- internal/handler/static_test.go | 46 +++++++++++++++++++ .../middleware/compression/gzip_static.go | 5 ++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/internal/handler/static.go b/internal/handler/static.go index 1c823e7..c488101 100644 --- a/internal/handler/static.go +++ b/internal/handler/static.go @@ -19,11 +19,12 @@ package handler import ( - "mime" "os" "path/filepath" "strings" + "rua.plus/lolly/internal/mimeutil" + "github.com/valyala/fasthttp" "rua.plus/lolly/internal/cache" "rua.plus/lolly/internal/middleware/compression" @@ -462,7 +463,7 @@ func (h *StaticHandler) serveFile(ctx *fasthttp.RequestCtx, filePath string, inf if entry.ModTime.Equal(info.ModTime()) { // 缓存命中且文件未修改 ctx.Response.SetBody(entry.Data) - ctx.Response.Header.SetContentType(mime.TypeByExtension(filepath.Ext(filePath))) + ctx.Response.Header.SetContentType(mimeutil.DetectContentType(filePath)) return } // 文件已修改,删除旧缓存 @@ -472,6 +473,9 @@ func (h *StaticHandler) serveFile(ctx *fasthttp.RequestCtx, filePath string, inf // 大文件使用零拷贝传输 if h.useSendfile && info.Size() >= MinSendfileSize { + // 设置 Content-Type (sendfile 不会自动设置) + ctx.Response.Header.SetContentType(mimeutil.DetectContentType(filePath)) + file, err := os.Open(filePath) if err == nil { defer func() { _ = file.Close() }() @@ -495,5 +499,5 @@ func (h *StaticHandler) serveFile(ctx *fasthttp.RequestCtx, filePath string, inf } ctx.Response.SetBody(data) - ctx.Response.Header.SetContentType(mime.TypeByExtension(filepath.Ext(filePath))) + ctx.Response.Header.SetContentType(mimeutil.DetectContentType(filePath)) } diff --git a/internal/handler/static_test.go b/internal/handler/static_test.go index f8fe4e1..a2a8e4b 100644 --- a/internal/handler/static_test.go +++ b/internal/handler/static_test.go @@ -1402,3 +1402,49 @@ func TestStaticHandler_TryFilesEdgeCases(t *testing.T) { }) } } + +// TestStaticHandler_LargeFileContentType 测试大文件 sendfile 路径的 Content-Type +func TestStaticHandler_LargeFileContentType(t *testing.T) { + tmpDir := t.TempDir() + + // 创建大于 8KB 的文件 + largeContent := make([]byte, 16*1024) + for i := range largeContent { + largeContent[i] = byte(i % 256) + } + + tests := []struct { + ext string + expected string + }{ + {".css", "text/css; charset=utf-8"}, + {".js", "text/javascript; charset=utf-8"}, + {".webmanifest", "application/manifest+json"}, + {".webm", "video/webm"}, + {".otf", "font/otf"}, + } + + for _, tc := range tests { + t.Run(tc.ext, func(t *testing.T) { + filePath := filepath.Join(tmpDir, "large"+tc.ext) + if err := os.WriteFile(filePath, largeContent, 0644); err != nil { + t.Fatalf("创建文件失败: %v", err) + } + + handler := NewStaticHandler(tmpDir, "/", nil, true) // 启用 sendfile + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/large" + tc.ext) + + handler.Handle(ctx) + + if got := ctx.Response.StatusCode(); got != fasthttp.StatusOK { + t.Errorf("状态码 = %d, want %d", got, fasthttp.StatusOK) + } + + ct := string(ctx.Response.Header.ContentType()) + if ct != tc.expected { + t.Errorf("Content-Type = %q, want %q", ct, tc.expected) + } + }) + } +} diff --git a/internal/middleware/compression/gzip_static.go b/internal/middleware/compression/gzip_static.go index 428d8cf..73fd9e9 100644 --- a/internal/middleware/compression/gzip_static.go +++ b/internal/middleware/compression/gzip_static.go @@ -20,6 +20,7 @@ import ( "strings" "github.com/valyala/fasthttp" + "rua.plus/lolly/internal/mimeutil" ) // GzipStatic 预压缩文件支持。 @@ -112,6 +113,10 @@ func (g *GzipStatic) ServeFile(ctx *fasthttp.RequestCtx, filePath string) bool { ctx.Response.Header.Set("Content-Encoding", "gzip") } ctx.Response.Header.Set("Vary", "Accept-Encoding") + // 设置原始文件的 Content-Type + // filePath 是原始文件路径 (如 "test.js"),直接使用即可 + ctx.Response.Header.SetContentType(mimeutil.DetectContentType(filePath)) + fasthttp.ServeFile(ctx, fullPath) return true }