feat: better caching headers for images

This commit is contained in:
2026-03-09 13:35:23 +01:00
parent a27b4a66e6
commit 2b07701762
11 changed files with 83 additions and 43 deletions
-1
View File
@@ -52,7 +52,6 @@ func Path(ctx context.Context, pp ...string) string {
} }
func PathForLang(ctx context.Context, lang string, 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) s, ok := ctx.Value(serviceKey).(*Service)
if !ok { if !ok {
slog.Error("could not extract translation service from context for translating path", "ctx", ctx, "path", pp) slog.Error("could not extract translation service from context for translating path", "ctx", ctx, "path", pp)
@@ -1,4 +1,4 @@
package restutil package rest
import ( import (
"encoding/json" "encoding/json"
@@ -1,4 +1,4 @@
package restutil package rest
import ( import (
"fmt" "fmt"
+13
View File
@@ -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
}
+6 -3
View File
@@ -10,14 +10,17 @@ import (
) )
type Handler struct { type Handler struct {
http.Handler
src pageService src pageService
r renderer r renderer
} }
func New(mux *http.ServeMux, renderer renderer, srv pageService) *Handler { func New(renderer renderer, srv pageService) *Handler {
mux := http.NewServeMux()
h := &Handler{ h := &Handler{
src: srv, Handler: mux,
r: renderer, src: srv,
r: renderer,
} }
mux.HandleFunc("GET /{$}", h.home) mux.HandleFunc("GET /{$}", h.home)
+7 -7
View File
@@ -6,7 +6,7 @@ import (
"net/http" "net/http"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/images" "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) { 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 var err error
options := images.Options{} options := images.Options{}
options.Height, err = restutil.IntParam(r, "h") options.Height, err = rest.IntParam(r, "h")
if err != nil { if err != nil {
restutil.SendErr(w, err) rest.SendErr(w, err)
return return
} }
options.Width, err = restutil.IntParam(r, "w") options.Width, err = rest.IntParam(r, "w")
if err != nil { if err != nil {
restutil.SendErr(w, err) rest.SendErr(w, err)
return return
} }
options.Quality, err = restutil.IntParam(r, "q") options.Quality, err = rest.IntParam(r, "q")
if err != nil { if err != nil {
restutil.SendErr(w, err) rest.SendErr(w, err)
return return
} }
+5 -2
View File
@@ -9,12 +9,15 @@ import (
) )
type Handler struct { type Handler struct {
http.Handler
img imageService img imageService
} }
func New(mux *http.ServeMux, srv imageService) *Handler { func New(srv imageService) *Handler {
mux := http.NewServeMux()
h := &Handler{ h := &Handler{
img: srv, Handler: mux,
img: srv,
} }
mux.HandleFunc("GET /images/{uid}", h.getImage) mux.HandleFunc("GET /images/{uid}", h.getImage)
@@ -1,4 +1,4 @@
package server package middleware
import ( import (
"fmt" "fmt"
@@ -17,7 +17,7 @@ func (c *captureWriter) WriteHeader(statusCode int) {
c.ResponseWriter.WriteHeader(statusCode) 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) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now() start := time.Now()
cw := &captureWriter{ cw := &captureWriter{
@@ -35,7 +35,7 @@ func (s *Server) loggingMiddleware(next http.Handler) http.Handler {
} }
// cacheMiddleware adds Cache-Control headers to the response. // 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 func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%.0f", maxAge.Seconds())) w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%.0f", maxAge.Seconds()))
@@ -1,40 +1,50 @@
package server package server
import ( import (
"log/slog" "fmt"
"net/http" "net/http"
"time" "time"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/images" "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/page"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/translate" "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/resthome"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/restimage" "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/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/internal/pkg/templer"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web/layouts" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web/layouts"
"github.com/studio-b12/gowebdav" "github.com/studio-b12/gowebdav"
"github.com/valkey-io/valkey-go" "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() mux := http.NewServeMux()
registerStatic(mux) registerStatic(mux)
mux.Handle("/", s.registerOther())
other, err := s.dynamicRoutes()
if err != nil {
return
}
mux.Handle("/", other)
h = mux h = mux
h = templer.Middleware(h) h = templer.Middleware(h)
h = s.loggingMiddleware(h) h = middleware.Logging(h)
return h return h, nil
} }
func (s *Server) registerOther() (h http.Handler) { func (s *Server) dynamicRoutes() (h http.Handler, err error) {
mux := http.NewServeMux() mux := http.NewServeMux()
webdavClient := gowebdav.NewClient(s.cfg.FileSystem.URL, s.cfg.FileSystem.User, s.cfg.FileSystem.Password) webdavClient := gowebdav.NewClient(s.cfg.FileSystem.URL, s.cfg.FileSystem.User, s.cfg.FileSystem.Password)
if err := webdavClient.Connect(); err != nil { err = webdavClient.Connect()
slog.Error("could not connect webdavClient", "err", err) if err != nil {
err = fmt.Errorf("could not connect webdavClient: %w", err)
return
} }
var fs filesystem.FS = webdavClient var fs filesystem.FS = webdavClient
@@ -44,22 +54,27 @@ func (s *Server) registerOther() (h http.Handler) {
Password: s.cfg.Cache.Password, Password: s.cfg.Cache.Password,
}) })
if err != nil { 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 { } else {
fs = filesystem.NewCachedClient(webdavClient, valkeyClient, 5*time.Hour) fs = filesystem.NewCachedClient(webdavClient, valkeyClient, 5*time.Hour)
} }
renderer := layouts.New(s.cfg) mux.Handle("/images/", rest.Use(
restimage.New(images.New(fs, s.cfg.Image, valkeyClient)),
_ = resthome.New(mux, renderer, page.New(fs)) middleware.Cache(time.Hour*24*5),
_ = restimage.New(mux, images.New(fs, s.cfg.Image, valkeyClient)) ))
mux.Handle("/", http.FileServer(http.FS(web.GetStaticFS())))
translator := translate.New() translator := translate.New()
renderer := layouts.New(s.cfg)
h = mux mux.Handle("/", rest.Use(
h = translator.Middleware(h) resthome.New(renderer, page.New(fs)),
h = cacheMiddleware(time.Minute * 5)(h) translator.Middleware,
return h middleware.Cache(time.Minute),
))
return mux, nil
} }
+9 -3
View File
@@ -27,10 +27,16 @@ func Start(ctx context.Context, cfg config.Cfg) (err error) {
cfg: cfg, cfg: cfg,
} }
hander, err := s.allRoutes()
if err != nil {
err = fmt.Errorf("could not setup routes: %w", err)
return
}
// Declare Server config // Declare Server config
srv := &http.Server{ srv := &http.Server{
Addr: fmt.Sprintf(":%d", s.port), Addr: fmt.Sprintf(":%d", s.port),
Handler: s.RegisterRoutes(), Handler: hander,
IdleTimeout: time.Minute, IdleTimeout: time.Minute,
ReadTimeout: 10 * time.Second, ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * 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 // Shutdown server when ctx done
if err = srv.Shutdown(context.Background()); err != nil { if err = srv.Shutdown(context.Background()); err != nil {
// Error from closing listeners, or context timeout: // 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) close(idleConnsClosed)
@@ -53,7 +59,7 @@ func Start(ctx context.Context, cfg config.Cfg) (err error) {
slog.Info("starting server", "address", srv.Addr) slog.Info("starting server", "address", srv.Addr)
if err := srv.ListenAndServe(); err != http.ErrServerClosed { 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 <-idleConnsClosed
+3 -2
View File
@@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"time" "time"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/pkg/middleware"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web"
) )
@@ -14,7 +15,7 @@ func registerStatic(mux *http.ServeMux) {
fileSystem := web.GetStaticFS() fileSystem := web.GetStaticFS()
// we still use go's fileServer to avoid unnecessary implementation // 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 { err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil { if err != nil {
@@ -26,7 +27,7 @@ func registerStatic(mux *http.ServeMux) {
path = "/" + path path = "/" + path
slog.Debug("registering file", "path", path) slog.Debug("registered static file", "path", path)
mux.Handle(path, fileServer) mux.Handle(path, fileServer)