From 2b07701762492669f5dcfa7294f4d17f840fc42e Mon Sep 17 00:00:00 2001 From: schreifuchs Date: Mon, 9 Mar 2026 13:31:20 +0100 Subject: [PATCH] feat: better caching headers for images --- internal/components/translate/context.go | 1 - internal/handlers/{restutil => rest}/error.go | 2 +- .../handlers/{restutil => rest}/params.go | 2 +- internal/handlers/rest/use.go | 13 +++++ internal/handlers/resthome/resource.go | 9 ++- internal/handlers/restimage/conroller.go | 14 ++--- internal/handlers/restimage/resource.go | 7 ++- .../middleware/cache.go} | 6 +- internal/server/{routes.go => factory.go} | 55 ++++++++++++------- internal/server/server.go | 12 +++- internal/server/static.go | 5 +- 11 files changed, 83 insertions(+), 43 deletions(-) rename internal/handlers/{restutil => rest}/error.go (98%) rename internal/handlers/{restutil => rest}/params.go (95%) create mode 100644 internal/handlers/rest/use.go rename internal/{server/middlewares.go => pkg/middleware/cache.go} (85%) rename internal/server/{routes.go => factory.go} (58%) diff --git a/internal/components/translate/context.go b/internal/components/translate/context.go index ab9efce..3489d2c 100644 --- a/internal/components/translate/context.go +++ b/internal/components/translate/context.go @@ -52,7 +52,6 @@ func Path(ctx context.Context, pp ...string) string { } func PathForLang(ctx context.Context, lang string, pp ...string) string { - slog.Info("path for lang", "path", pp) s, ok := ctx.Value(serviceKey).(*Service) if !ok { slog.Error("could not extract translation service from context for translating path", "ctx", ctx, "path", pp) diff --git a/internal/handlers/restutil/error.go b/internal/handlers/rest/error.go similarity index 98% rename from internal/handlers/restutil/error.go rename to internal/handlers/rest/error.go index d264e62..d36be16 100644 --- a/internal/handlers/restutil/error.go +++ b/internal/handlers/rest/error.go @@ -1,4 +1,4 @@ -package restutil +package rest import ( "encoding/json" diff --git a/internal/handlers/restutil/params.go b/internal/handlers/rest/params.go similarity index 95% rename from internal/handlers/restutil/params.go rename to internal/handlers/rest/params.go index 1bbf153..fc08544 100644 --- a/internal/handlers/restutil/params.go +++ b/internal/handlers/rest/params.go @@ -1,4 +1,4 @@ -package restutil +package rest import ( "fmt" diff --git a/internal/handlers/rest/use.go b/internal/handlers/rest/use.go new file mode 100644 index 0000000..7f8b1da --- /dev/null +++ b/internal/handlers/rest/use.go @@ -0,0 +1,13 @@ +package rest + +import "net/http" + +type Middeware = func(next http.Handler) http.Handler + +func Use(handler http.Handler, middlewares ...Middeware) http.Handler { + for _, middleware := range middlewares { + handler = middleware(handler) + } + + return handler +} diff --git a/internal/handlers/resthome/resource.go b/internal/handlers/resthome/resource.go index a9b78f1..ee5fb28 100644 --- a/internal/handlers/resthome/resource.go +++ b/internal/handlers/resthome/resource.go @@ -10,14 +10,17 @@ import ( ) type Handler struct { + http.Handler src pageService r renderer } -func New(mux *http.ServeMux, renderer renderer, srv pageService) *Handler { +func New(renderer renderer, srv pageService) *Handler { + mux := http.NewServeMux() h := &Handler{ - src: srv, - r: renderer, + Handler: mux, + src: srv, + r: renderer, } mux.HandleFunc("GET /{$}", h.home) diff --git a/internal/handlers/restimage/conroller.go b/internal/handlers/restimage/conroller.go index 03645db..b25f095 100644 --- a/internal/handlers/restimage/conroller.go +++ b/internal/handlers/restimage/conroller.go @@ -6,7 +6,7 @@ import ( "net/http" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/images" - "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/restutil" + "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/rest" ) func (h *Handler) getImage(w http.ResponseWriter, r *http.Request) { @@ -14,20 +14,20 @@ func (h *Handler) getImage(w http.ResponseWriter, r *http.Request) { var err error options := images.Options{} - options.Height, err = restutil.IntParam(r, "h") + options.Height, err = rest.IntParam(r, "h") if err != nil { - restutil.SendErr(w, err) + rest.SendErr(w, err) return } - options.Width, err = restutil.IntParam(r, "w") + options.Width, err = rest.IntParam(r, "w") if err != nil { - restutil.SendErr(w, err) + rest.SendErr(w, err) return } - options.Quality, err = restutil.IntParam(r, "q") + options.Quality, err = rest.IntParam(r, "q") if err != nil { - restutil.SendErr(w, err) + rest.SendErr(w, err) return } diff --git a/internal/handlers/restimage/resource.go b/internal/handlers/restimage/resource.go index 89f7b5f..d83dd3e 100644 --- a/internal/handlers/restimage/resource.go +++ b/internal/handlers/restimage/resource.go @@ -9,12 +9,15 @@ import ( ) type Handler struct { + http.Handler img imageService } -func New(mux *http.ServeMux, srv imageService) *Handler { +func New(srv imageService) *Handler { + mux := http.NewServeMux() h := &Handler{ - img: srv, + Handler: mux, + img: srv, } mux.HandleFunc("GET /images/{uid}", h.getImage) diff --git a/internal/server/middlewares.go b/internal/pkg/middleware/cache.go similarity index 85% rename from internal/server/middlewares.go rename to internal/pkg/middleware/cache.go index a45be7b..b2dac1d 100644 --- a/internal/server/middlewares.go +++ b/internal/pkg/middleware/cache.go @@ -1,4 +1,4 @@ -package server +package middleware import ( "fmt" @@ -17,7 +17,7 @@ func (c *captureWriter) WriteHeader(statusCode int) { c.ResponseWriter.WriteHeader(statusCode) } -func (s *Server) loggingMiddleware(next http.Handler) http.Handler { +func Logging(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() cw := &captureWriter{ @@ -35,7 +35,7 @@ func (s *Server) loggingMiddleware(next http.Handler) http.Handler { } // cacheMiddleware adds Cache-Control headers to the response. -func cacheMiddleware(maxAge time.Duration) func(next http.Handler) http.Handler { +func Cache(maxAge time.Duration) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%.0f", maxAge.Seconds())) diff --git a/internal/server/routes.go b/internal/server/factory.go similarity index 58% rename from internal/server/routes.go rename to internal/server/factory.go index 0f9c706..a299a90 100644 --- a/internal/server/routes.go +++ b/internal/server/factory.go @@ -1,40 +1,50 @@ package server import ( - "log/slog" + "fmt" "net/http" "time" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/images" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/page" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/translate" + "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/rest" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/resthome" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/restimage" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/pkg/filesystem" + "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/pkg/middleware" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/pkg/templer" - "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web/layouts" "github.com/studio-b12/gowebdav" "github.com/valkey-io/valkey-go" ) -func (s *Server) RegisterRoutes() (h http.Handler) { +func (s *Server) allRoutes() (h http.Handler, err error) { mux := http.NewServeMux() registerStatic(mux) - mux.Handle("/", s.registerOther()) + + other, err := s.dynamicRoutes() + if err != nil { + return + } + + mux.Handle("/", other) h = mux h = templer.Middleware(h) - h = s.loggingMiddleware(h) - return h + h = middleware.Logging(h) + return h, nil } -func (s *Server) registerOther() (h http.Handler) { +func (s *Server) dynamicRoutes() (h http.Handler, err error) { mux := http.NewServeMux() + webdavClient := gowebdav.NewClient(s.cfg.FileSystem.URL, s.cfg.FileSystem.User, s.cfg.FileSystem.Password) - if err := webdavClient.Connect(); err != nil { - slog.Error("could not connect webdavClient", "err", err) + err = webdavClient.Connect() + if err != nil { + err = fmt.Errorf("could not connect webdavClient: %w", err) + return } var fs filesystem.FS = webdavClient @@ -44,22 +54,27 @@ func (s *Server) registerOther() (h http.Handler) { Password: s.cfg.Cache.Password, }) if err != nil { - slog.Error("failed to create valkey client", "err", err) + err = fmt.Errorf("failed to create valkey client: %w", err) + return + } + if err != nil { } else { fs = filesystem.NewCachedClient(webdavClient, valkeyClient, 5*time.Hour) } - renderer := layouts.New(s.cfg) - - _ = resthome.New(mux, renderer, page.New(fs)) - _ = restimage.New(mux, images.New(fs, s.cfg.Image, valkeyClient)) - - mux.Handle("/", http.FileServer(http.FS(web.GetStaticFS()))) + mux.Handle("/images/", rest.Use( + restimage.New(images.New(fs, s.cfg.Image, valkeyClient)), + middleware.Cache(time.Hour*24*5), + )) translator := translate.New() + renderer := layouts.New(s.cfg) - h = mux - h = translator.Middleware(h) - h = cacheMiddleware(time.Minute * 5)(h) - return h + mux.Handle("/", rest.Use( + resthome.New(renderer, page.New(fs)), + translator.Middleware, + middleware.Cache(time.Minute), + )) + + return mux, nil } diff --git a/internal/server/server.go b/internal/server/server.go index 6d54344..479c3ca 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -27,10 +27,16 @@ func Start(ctx context.Context, cfg config.Cfg) (err error) { cfg: cfg, } + hander, err := s.allRoutes() + if err != nil { + err = fmt.Errorf("could not setup routes: %w", err) + return + } + // Declare Server config srv := &http.Server{ Addr: fmt.Sprintf(":%d", s.port), - Handler: s.RegisterRoutes(), + Handler: hander, IdleTimeout: time.Minute, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, @@ -45,7 +51,7 @@ func Start(ctx context.Context, cfg config.Cfg) (err error) { // Shutdown server when ctx done if err = srv.Shutdown(context.Background()); err != nil { // Error from closing listeners, or context timeout: - err = fmt.Errorf("error whilse shutting down server: %w", err) + err = fmt.Errorf("error while shutting down server: %w", err) } close(idleConnsClosed) @@ -53,7 +59,7 @@ func Start(ctx context.Context, cfg config.Cfg) (err error) { slog.Info("starting server", "address", srv.Addr) if err := srv.ListenAndServe(); err != http.ErrServerClosed { - return fmt.Errorf("error while server ListenAndServe: %w", err) + return fmt.Errorf("error while serving: %w", err) } <-idleConnsClosed diff --git a/internal/server/static.go b/internal/server/static.go index 6345f16..bd924da 100644 --- a/internal/server/static.go +++ b/internal/server/static.go @@ -6,6 +6,7 @@ import ( "net/http" "time" + "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/pkg/middleware" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web" ) @@ -14,7 +15,7 @@ func registerStatic(mux *http.ServeMux) { fileSystem := web.GetStaticFS() // we still use go's fileServer to avoid unnecessary implementation - fileServer := cacheMiddleware(time.Hour * 24)(http.FileServer(http.FS(fileSystem))) + fileServer := middleware.Cache(time.Hour * 24)(http.FileServer(http.FS(fileSystem))) err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { @@ -26,7 +27,7 @@ func registerStatic(mux *http.ServeMux) { path = "/" + path - slog.Debug("registering file", "path", path) + slog.Debug("registered static file", "path", path) mux.Handle(path, fileServer)