mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-20 10:33:41 -05:00
server: add Cache-Control and ETag headers
This commit is contained in:
parent
b24a34ef97
commit
e243593e06
3 changed files with 40 additions and 4 deletions
|
@ -57,7 +57,7 @@ var ErrBadGateway = mautrix.RespError{
|
||||||
StatusCode: http.StatusBadGateway,
|
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 !entry.UseCache() {
|
||||||
if force {
|
if force {
|
||||||
mautrix.MNotFound.WithMessage("Media not found in cache").Write(w)
|
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")
|
w.Header().Set("Mau-Cached-Error", "true")
|
||||||
entry.Error.Write(w)
|
entry.Error.Write(w)
|
||||||
return true
|
return true
|
||||||
|
} else if r.Header.Get("If-None-Match") == entry.ETag() {
|
||||||
|
w.WriteHeader(http.StatusNotModified)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
log := zerolog.Ctx(ctx)
|
log := zerolog.Ctx(ctx)
|
||||||
cacheFile, err := os.Open(gmx.cacheEntryToPath(entry.Hash[:]))
|
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-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-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("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 {
|
type noErrorWriter struct {
|
||||||
|
@ -193,7 +198,7 @@ func (gmx *Gomuks) DownloadMedia(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if gmx.downloadMediaFromCache(ctx, w, cacheEntry, false) {
|
if gmx.downloadMediaFromCache(ctx, w, r, cacheEntry, false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,7 +324,7 @@ func (gmx *Gomuks) DownloadMedia(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if w != nil {
|
if w != nil {
|
||||||
gmx.downloadMediaFromCache(ctx, w, cacheEntry, true)
|
gmx.downloadMediaFromCache(ctx, w, r, cacheEntry, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
@ -64,7 +65,7 @@ func (gmx *Gomuks) StartServer() {
|
||||||
if frontend, err := fs.Sub(web.Frontend, "dist"); err != nil {
|
if frontend, err := fs.Sub(web.Frontend, "dist"); err != nil {
|
||||||
gmx.Log.Warn().Msg("Frontend not found")
|
gmx.Log.Warn().Msg("Frontend not found")
|
||||||
} else {
|
} else {
|
||||||
router.Handle("/", http.FileServerFS(frontend))
|
router.Handle("/", FrontendCacheMiddleware(http.FileServerFS(frontend)))
|
||||||
}
|
}
|
||||||
gmx.Server = &http.Server{
|
gmx.Server = &http.Server{
|
||||||
Addr: gmx.Config.Web.ListenAddress,
|
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")
|
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 (
|
var (
|
||||||
ErrInvalidHeader = mautrix.RespError{ErrCode: "FI.MAU.GOMUKS.INVALID_HEADER", StatusCode: http.StatusForbidden}
|
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}
|
ErrMissingCookie = mautrix.RespError{ErrCode: "FI.MAU.GOMUKS.MISSING_COOKIE", Err: "Missing gomuks_auth cookie", StatusCode: http.StatusUnauthorized}
|
||||||
|
|
|
@ -9,6 +9,7 @@ package database
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"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.error_ts"] = me.ReceivedAt.UnixMilli()
|
||||||
me.Matrix.ExtraData["fi.mau.hicli.next_retry_ts"] = me.ReceivedAt.Add(me.backoff()).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)
|
me.Matrix.WithStatus(me.StatusCode).Write(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +109,13 @@ type Media struct {
|
||||||
Error *MediaError
|
Error *MediaError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Media) ETag() string {
|
||||||
|
if m.Hash == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`"%x"`, m.Hash)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Media) UseCache() bool {
|
func (m *Media) UseCache() bool {
|
||||||
return m != nil && (m.Hash != nil || m.Error.UseCache())
|
return m != nil && (m.Hash != nil || m.Error.UseCache())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue