From 688e941d645160b95ba8b8f73bbcdd417f2d5a73 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Tue, 27 May 2025 16:29:58 +0100 Subject: [PATCH] router: implement ETags/If-None-Match based on build time Essentially caching based on when the program was built. --- go.mod | 3 +-- go.sum | 4 ---- router.go | 7 +++---- static.go | 29 +++++++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 7157af4..60e3a14 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,6 @@ require ( github.com/fsnotify/fsnotify v1.8.0 github.com/getlantern/systray v1.2.2 github.com/gin-contrib/pprof v1.5.0 - github.com/gin-contrib/static v1.1.2 github.com/gin-gonic/gin v1.10.0 github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible @@ -43,6 +42,7 @@ require ( github.com/hrfee/jfa-go/logger v0.0.0-20241105225412-da4470bc4fbc github.com/hrfee/jfa-go/logmessages v0.0.0-20241105225412-da4470bc4fbc github.com/hrfee/jfa-go/ombi v0.0.0-20241105225412-da4470bc4fbc + github.com/hrfee/mediabrowser v0.3.28 github.com/itchyny/timefmt-go v0.1.6 github.com/lithammer/shortuuid/v3 v3.0.7 github.com/mailgun/mailgun-go/v4 v4.18.1 @@ -97,7 +97,6 @@ require ( github.com/google/flatbuffers v24.3.25+incompatible // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/hrfee/mediabrowser v0.3.28 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.11 // indirect diff --git a/go.sum b/go.sum index 04f8119..1084470 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,6 @@ github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NB github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-contrib/static v1.1.2 h1:c3kT4bFkUJn2aoRU3s6XnMjJT8J6nNWJkR0NglqmlZ4= -github.com/gin-contrib/static v1.1.2/go.mod h1:Fw90ozjHCmZBWbgrsqrDvO28YbhKEKzKp8GixhR4yLw= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= @@ -205,8 +203,6 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hrfee/mediabrowser v0.3.27 h1:8bxPamBFLD1Xqy6pf6M3Oc5GUQ0iU/flO0S64G1AsIM= -github.com/hrfee/mediabrowser v0.3.27/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U= github.com/hrfee/mediabrowser v0.3.28 h1:KkSgODXxUnZLrkmjSWpma8mXwEVxlOtI51uS2QP/e+c= github.com/hrfee/mediabrowser v0.3.28/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= diff --git a/router.go b/router.go index 4fa3952..2acb221 100644 --- a/router.go +++ b/router.go @@ -9,7 +9,6 @@ import ( "github.com/fatih/color" "github.com/gin-contrib/pprof" - "github.com/gin-contrib/static" "github.com/gin-gonic/gin" lm "github.com/hrfee/jfa-go/logmessages" swaggerFiles "github.com/swaggo/files" @@ -94,7 +93,7 @@ func (app *appContext) loadRouter(address string, debug bool) *gin.Engine { router.Use(gin.Recovery()) app.loadHTML(router) - router.Use(static.Serve("/", app.webFS)) + router.Use(serveTaggedStatic("/", app.webFS)) router.NoRoute(app.NoRouteHandler) if *PPROF { app.debug.Println(lm.RegisterPprof) @@ -117,7 +116,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) { for _, p := range routePrefixes { router.GET(p+"/lang/:page", app.GetLanguages) - router.Use(static.Serve(p+"/", app.webFS)) + router.Use(serveTaggedStatic(p+"/", app.webFS)) router.GET(p+PAGES.Admin, app.AdminPage) if app.config.Section("password_resets").Key("link_reset").MustBool(false) { @@ -136,7 +135,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) { router.GET(p+"/token/login", app.getTokenLogin) router.GET(p+"/token/refresh", app.getTokenRefresh) router.POST(p+"/user/invite", app.NewUserFromInvite) - router.Use(static.Serve(p+PAGES.Form+"/", app.webFS)) + router.Use(serveTaggedStatic(p+PAGES.Form+"/", app.webFS)) router.GET(p+PAGES.Form+"/:invCode", app.InviteProxy) if app.config.Section("captcha").Key("enabled").MustBool(false) { router.GET(p+"/captcha/gen/:invCode", app.GenCaptcha) diff --git a/static.go b/static.go index 6102d83..db98598 100644 --- a/static.go +++ b/static.go @@ -4,6 +4,8 @@ import ( "io/fs" "net/http" "strings" + + "github.com/gin-gonic/gin" ) // Since the gin-static middleware uses a version of http.Filesystem with an extra Exists() func, we extend it here. @@ -30,3 +32,30 @@ func (f httpFS) Exists(prefix string, filepath string) bool { } return false } + +var ( + etag = buildTimeUnix +) + +// Use unix build time as the ETag for a request, allowing caching of static files. +// Copied from gin-contrib/static: +// https://github.com/gin-gonic/contrib/blob/2b1292699c15c6bc6ee8f0e801a4d0b4e807f366/static/static.go +func serveTaggedStatic(urlPrefix string, fs httpFS) gin.HandlerFunc { + fileserver := http.FileServer(fs) + if urlPrefix != "" { + fileserver = http.StripPrefix(urlPrefix, fileserver) + } + return func(gc *gin.Context) { + if fs.Exists(urlPrefix, gc.Request.URL.Path) { + gc.Header("Cache-Control", "no-cache") + gc.Header("ETag", buildTimeUnix) + ifNoneMatchTag := gc.Request.Header.Get("If-None-Match") + if ifNoneMatchTag != "" && ifNoneMatchTag == etag && (gc.Request.Method == http.MethodGet || gc.Request.Method == http.MethodHead) { + gc.AbortWithStatus(http.StatusNotModified) + } else { + fileserver.ServeHTTP(gc.Writer, gc.Request) + gc.Abort() + } + } + } +}