server: add Cache-Control and ETag headers

This commit is contained in:
Tulir Asokan 2024-10-23 02:08:17 +03:00
parent b24a34ef97
commit e243593e06
3 changed files with 40 additions and 4 deletions

View file

@ -57,7 +57,7 @@ var ErrBadGateway = mautrix.RespError{
StatusCode: http.StatusBadGateway,
}
func (gmx *Gomuks) downloadMediaFromCache(ctx context.Context, w http.ResponseWriter, entry *database.Media, force bool) bool {
func (gmx *Gomuks) downloadMediaFromCache(ctx context.Context, w http.ResponseWriter, r *http.Request, entry *database.Media, force bool) bool {
if !entry.UseCache() {
if force {
mautrix.MNotFound.WithMessage("Media not found in cache").Write(w)
@ -69,6 +69,9 @@ func (gmx *Gomuks) downloadMediaFromCache(ctx context.Context, w http.ResponseWr
w.Header().Set("Mau-Cached-Error", "true")
entry.Error.Write(w)
return true
} else if r.Header.Get("If-None-Match") == entry.ETag() {
w.WriteHeader(http.StatusNotModified)
return true
}
log := zerolog.Ctx(ctx)
cacheFile, err := os.Open(gmx.cacheEntryToPath(entry.Hash[:]))
@ -102,6 +105,8 @@ func cacheEntryToHeaders(w http.ResponseWriter, entry *database.Media) {
w.Header().Set("Content-Length", strconv.FormatInt(entry.Size, 10))
w.Header().Set("Content-Disposition", mime.FormatMediaType(entry.ContentDisposition(), map[string]string{"filename": entry.FileName}))
w.Header().Set("Content-Security-Policy", "sandbox; default-src 'none'; script-src 'none';")
w.Header().Set("Cache-Control", "max-age=2592000, immutable")
w.Header().Set("ETag", entry.ETag())
}
type noErrorWriter struct {
@ -193,7 +198,7 @@ func (gmx *Gomuks) DownloadMedia(w http.ResponseWriter, r *http.Request) {
return
}
if gmx.downloadMediaFromCache(ctx, w, cacheEntry, false) {
if gmx.downloadMediaFromCache(ctx, w, r, cacheEntry, false) {
return
}
@ -319,7 +324,7 @@ func (gmx *Gomuks) DownloadMedia(w http.ResponseWriter, r *http.Request) {
return
}
if w != nil {
gmx.downloadMediaFromCache(ctx, w, cacheEntry, true)
gmx.downloadMediaFromCache(ctx, w, r, cacheEntry, true)
}
}

View file

@ -22,6 +22,7 @@ import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/fs"
"net/http"
_ "net/http/pprof"
@ -64,7 +65,7 @@ func (gmx *Gomuks) StartServer() {
if frontend, err := fs.Sub(web.Frontend, "dist"); err != nil {
gmx.Log.Warn().Msg("Frontend not found")
} else {
router.Handle("/", http.FileServerFS(frontend))
router.Handle("/", FrontendCacheMiddleware(http.FileServerFS(frontend)))
}
gmx.Server = &http.Server{
Addr: gmx.Config.Web.ListenAddress,
@ -79,6 +80,26 @@ func (gmx *Gomuks) StartServer() {
gmx.Log.Info().Str("address", gmx.Config.Web.ListenAddress).Msg("Server started")
}
func FrontendCacheMiddleware(next http.Handler) http.Handler {
var frontendCacheETag string
if Commit != "unknown" && !ParsedBuildTime.IsZero() {
frontendCacheETag = fmt.Sprintf(`"%s-%s"`, Commit, ParsedBuildTime.Format(time.RFC3339))
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("If-None-Match") == frontendCacheETag {
w.WriteHeader(http.StatusNotModified)
return
}
if strings.HasPrefix(r.URL.Path, "/assets/") {
w.Header().Set("Cache-Control", "max-age=604800, immutable")
}
if frontendCacheETag != "" {
w.Header().Set("ETag", frontendCacheETag)
}
next.ServeHTTP(w, r)
})
}
var (
ErrInvalidHeader = mautrix.RespError{ErrCode: "FI.MAU.GOMUKS.INVALID_HEADER", StatusCode: http.StatusForbidden}
ErrMissingCookie = mautrix.RespError{ErrCode: "FI.MAU.GOMUKS.MISSING_COOKIE", Err: "Missing gomuks_auth cookie", StatusCode: http.StatusUnauthorized}

View file

@ -9,6 +9,7 @@ package database
import (
"context"
"database/sql"
"fmt"
"net/http"
"slices"
"time"
@ -93,6 +94,8 @@ func (me *MediaError) Write(w http.ResponseWriter) {
}
me.Matrix.ExtraData["fi.mau.hicli.error_ts"] = me.ReceivedAt.UnixMilli()
me.Matrix.ExtraData["fi.mau.hicli.next_retry_ts"] = me.ReceivedAt.Add(me.backoff()).UnixMilli()
w.Header().Set("Mau-Errored-At", me.ReceivedAt.Format(http.TimeFormat))
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", max(int(time.Until(me.ReceivedAt.Add(me.backoff())).Seconds()), 0)))
me.Matrix.WithStatus(me.StatusCode).Write(w)
}
@ -106,6 +109,13 @@ type Media struct {
Error *MediaError
}
func (m *Media) ETag() string {
if m.Hash == nil {
return ""
}
return fmt.Sprintf(`"%x"`, m.Hash)
}
func (m *Media) UseCache() bool {
return m != nil && (m.Hash != nil || m.Error.UseCache())
}