feat: better caching headers for images
This commit is contained in:
@@ -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"
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -10,12 +10,15 @@ 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{
|
||||||
|
Handler: mux,
|
||||||
src: srv,
|
src: srv,
|
||||||
r: renderer,
|
r: renderer,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,14 @@ 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{
|
||||||
|
Handler: mux,
|
||||||
img: srv,
|
img: srv,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user