mirror of
https://codeberg.org/readeck/readeck.git
synced 2025-12-22 13:17:10 +00:00
New Etagger interface
- new Etagger that must implement a GetSumReader method - fixed "tager" spelling - use fnv instead of crc64 to hash etag values
This commit is contained in:
@@ -67,6 +67,7 @@ linters:
|
||||
- fmt.Fprintf(net/http.ResponseWriter)
|
||||
- fmt.Fprintln(net/http.ResponseWriter)
|
||||
- io.Copy(net/http.ResponseWriter)
|
||||
- io.WriteString(hash.Hash)
|
||||
- io.WriteString(net/http.ResponseWriter)
|
||||
- (*strings.Replacer).WriteString(net/http.ResponseWriter)
|
||||
- (*codeberg.org/readeck/readeck/internal/server.Server).AddFlash
|
||||
|
||||
@@ -8,6 +8,7 @@ package docs
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"hash"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
)
|
||||
@@ -43,9 +44,9 @@ type Manifest struct {
|
||||
|
||||
var manifest *Manifest
|
||||
|
||||
// GetSumStrings implements the Etager interface.
|
||||
func (f *File) GetSumStrings() []string {
|
||||
return []string{f.Etag}
|
||||
// UpdateEtag implements the [server.Etagger] interface.
|
||||
func (f *File) UpdateEtag(h hash.Hash) {
|
||||
h.Write([]byte(f.Etag))
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -7,6 +7,8 @@ package assets
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -42,8 +44,8 @@ func newRandom(data uint64) *random {
|
||||
return &random{rand.New(rand.NewPCG(data, data))} //nolint:gosec
|
||||
}
|
||||
|
||||
func (r *random) GetSumStrings() []string {
|
||||
return []string{configs.BuildTime().String(), strconv.Itoa(r.Int())}
|
||||
func (r *random) UpdateEtag(h hash.Hash) {
|
||||
io.WriteString(h, configs.BuildTime().String()+strconv.Itoa(r.Int()))
|
||||
}
|
||||
|
||||
func (r *random) GetLastModified() []time.Time {
|
||||
@@ -53,8 +55,6 @@ func (r *random) GetLastModified() []time.Time {
|
||||
// randomSvg sends an SVG image with a gradient. The gradient's color
|
||||
// is based on the name.
|
||||
func randomSvg(s *server.Server) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
|
||||
withHashCode := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
@@ -76,7 +76,7 @@ func randomSvg(s *server.Server) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
r.With(withHashCode).Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
return withHashCode(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rd := r.Context().Value(ctxNameKey{}).(*random)
|
||||
|
||||
w.Header().Set("Content-Type", "image/svg+xml")
|
||||
@@ -87,8 +87,7 @@ func randomSvg(s *server.Server) http.Handler {
|
||||
rd.Perm(70)[1]+20, // bottom saturation
|
||||
randomCircles(rd),
|
||||
)
|
||||
})
|
||||
return r
|
||||
}))
|
||||
}
|
||||
|
||||
func randomCircles(r *random) string {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
"log/slog"
|
||||
"math/rand/v2"
|
||||
"strconv"
|
||||
@@ -152,12 +154,9 @@ func (u *User) Delete() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSumStrings implements Etager interface.
|
||||
func (u *User) GetSumStrings() []string {
|
||||
return []string{
|
||||
strconv.Itoa(u.ID),
|
||||
strconv.FormatInt(u.Updated.Unix(), 10),
|
||||
}
|
||||
// UpdateEtag implements [server.Etagger] interface.
|
||||
func (u *User) UpdateEtag(h hash.Hash) {
|
||||
io.WriteString(h, u.UID+strconv.FormatInt(u.Updated.UTC().UnixNano(), 10))
|
||||
}
|
||||
|
||||
// GetLastModified implements LastModer interface.
|
||||
|
||||
@@ -10,11 +10,14 @@ import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -537,10 +540,10 @@ func (b *Bookmark) replaceLabel(oldLabel, newLabel string) {
|
||||
b.Labels = slices.Compact(b.Labels)
|
||||
}
|
||||
|
||||
// GetSumStrings returns the string used to generate the etag
|
||||
// UpdateEtag returns the string used to generate the etag
|
||||
// of the bookmark(s).
|
||||
func (b *Bookmark) GetSumStrings() []string {
|
||||
return []string{b.UID, b.Updated.String()}
|
||||
func (b *Bookmark) UpdateEtag(h hash.Hash) {
|
||||
io.WriteString(h, b.UID+strconv.FormatInt(b.Updated.UTC().UnixNano(), 10))
|
||||
}
|
||||
|
||||
// GetLastModified returns the last modified times.
|
||||
|
||||
@@ -6,6 +6,9 @@ package bookmarks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
@@ -123,8 +126,8 @@ func (c *Collection) Delete() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSumStrings returns the string used to generate the etag
|
||||
// UpdateEtag returns the string used to generate the etag
|
||||
// of the collection(s).
|
||||
func (c *Collection) GetSumStrings() []string {
|
||||
return []string{c.UID, c.Updated.String()}
|
||||
func (c *Collection) UpdateEtag(h hash.Hash) {
|
||||
io.WriteString(h, c.UID+strconv.FormatInt(c.Updated.UTC().UnixNano(), 10))
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -35,17 +36,17 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
ctxAnnotationListKey struct{}
|
||||
ctxBookmarkKey struct{}
|
||||
ctxBookmarkListKey struct{}
|
||||
ctxBookmarkListTagerKey struct{}
|
||||
ctxBookmarkOrderKey struct{}
|
||||
ctxBookmarkSyncListKey struct{}
|
||||
ctxLabelKey struct{}
|
||||
ctxLabelListKey struct{}
|
||||
ctxSharedInfoKey struct{}
|
||||
ctxFiltersKey struct{}
|
||||
ctxDefaultLimitKey struct{}
|
||||
ctxAnnotationListKey struct{}
|
||||
ctxBookmarkKey struct{}
|
||||
ctxBookmarkListKey struct{}
|
||||
ctxBookmarkListTaggerKey struct{}
|
||||
ctxBookmarkOrderKey struct{}
|
||||
ctxBookmarkSyncListKey struct{}
|
||||
ctxLabelKey struct{}
|
||||
ctxLabelListKey struct{}
|
||||
ctxSharedInfoKey struct{}
|
||||
ctxFiltersKey struct{}
|
||||
ctxDefaultLimitKey struct{}
|
||||
)
|
||||
|
||||
// bookmarkList renders a paginated list of the connected
|
||||
@@ -757,14 +758,14 @@ func (api *apiRouter) withBookmarkList(next http.Handler) http.Handler {
|
||||
ctx := filterForm.saveContext(r.Context())
|
||||
ctx = context.WithValue(ctx, ctxBookmarkListKey{}, res)
|
||||
|
||||
tagers := []server.Etager{res}
|
||||
t, ok := r.Context().Value(ctxBookmarkListTagerKey{}).([]server.Etager)
|
||||
taggers := []server.Etagger{res}
|
||||
t, ok := r.Context().Value(ctxBookmarkListTaggerKey{}).([]server.Etagger)
|
||||
if ok {
|
||||
tagers = append(tagers, t...)
|
||||
taggers = append(taggers, t...)
|
||||
}
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
api.srv.WriteEtag(w, r, tagers...)
|
||||
api.srv.WriteEtag(w, r, taggers...)
|
||||
}
|
||||
api.srv.WithCaching(next).ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
@@ -788,8 +789,8 @@ func (api *apiRouter) withBookmarkSyncList(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), ctxBookmarkSyncListKey{}, res)
|
||||
tagers := []server.Etager{res}
|
||||
api.srv.WriteEtag(w, r, tagers...)
|
||||
taggers := []server.Etagger{res}
|
||||
api.srv.WriteEtag(w, r, taggers...)
|
||||
|
||||
api.srv.WithCaching(next).ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
@@ -949,13 +950,10 @@ type bookmarkList struct {
|
||||
Items []bookmarkItem
|
||||
}
|
||||
|
||||
func (bl bookmarkList) GetSumStrings() []string {
|
||||
r := []string{}
|
||||
func (bl bookmarkList) UpdateEtag(h hash.Hash) {
|
||||
for i := range bl.items {
|
||||
r = append(r, bl.items[i].Updated.String(), bl.items[i].UID)
|
||||
io.WriteString(h, bl.items[i].UID+strconv.FormatInt(bl.items[i].Updated.UTC().Unix(), 10))
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// bookmarkItem is a serialized bookmark instance that can
|
||||
@@ -1221,13 +1219,10 @@ func (bi *bookmarkItem) setEmbed() error {
|
||||
|
||||
type bookmarkSyncList []*bookmarkSyncItem
|
||||
|
||||
func (bl bookmarkSyncList) GetSumStrings() []string {
|
||||
r := []string{}
|
||||
for i := range bl {
|
||||
r = append(r, bl[i].Updated.String(), bl[i].ID)
|
||||
func (bl bookmarkSyncList) UpdateEtag(h hash.Hash) {
|
||||
for _, b := range bl {
|
||||
io.WriteString(h, b.ID+strconv.FormatInt(b.Updated.UTC().Unix(), 10))
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
type bookmarkSyncItem struct {
|
||||
|
||||
@@ -157,7 +157,7 @@ func (api *apiRouter) withCollection(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), ctxCollectionKey{}, c)
|
||||
ctx = context.WithValue(ctx, ctxBookmarkListTagerKey{}, []server.Etager{c})
|
||||
ctx = context.WithValue(ctx, ctxBookmarkListTaggerKey{}, []server.Etagger{c})
|
||||
|
||||
if ctx.Value(ctxBookmarkOrderKey{}) == nil {
|
||||
ctx = context.WithValue(ctx, ctxBookmarkOrderKey{}, orderExpressionList{goqu.T("b").Col("created").Desc()})
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"hash/crc64"
|
||||
"hash"
|
||||
"hash/fnv"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -16,10 +17,9 @@ import (
|
||||
"codeberg.org/readeck/readeck/internal/auth"
|
||||
)
|
||||
|
||||
// Etager must provides a function that returns a list of
|
||||
// strings used to build an etag header.
|
||||
type Etager interface {
|
||||
GetSumStrings() []string
|
||||
// Etagger must provides a function that can update a [hash.Hash].
|
||||
type Etagger interface {
|
||||
UpdateEtag(hash.Hash)
|
||||
}
|
||||
|
||||
// LastModer must provides a function that returns a list
|
||||
@@ -37,15 +37,15 @@ const (
|
||||
)
|
||||
|
||||
// WriteEtag adds an Etag header to the response, based on
|
||||
// the values sent by GetSumStrings. The build date is always
|
||||
// the values from UpdateEtag. The build date is always
|
||||
// included.
|
||||
func (s *Server) WriteEtag(w http.ResponseWriter, r *http.Request, taggers ...Etager) {
|
||||
func (s *Server) WriteEtag(w http.ResponseWriter, r *http.Request, taggers ...Etagger) {
|
||||
if len(taggers) == 0 {
|
||||
w.Header().Del("Etag")
|
||||
return
|
||||
}
|
||||
|
||||
h := crc64.New(crc64.MakeTable(crc64.ISO))
|
||||
h := fnv.New64()
|
||||
h.Write([]byte(strconv.FormatInt(configs.BuildTime().Unix(), 10)))
|
||||
|
||||
if user := auth.GetRequestUser(r); user.ID != 0 {
|
||||
@@ -55,10 +55,11 @@ func (s *Server) WriteEtag(w http.ResponseWriter, r *http.Request, taggers ...Et
|
||||
taggers = append(taggers, sess)
|
||||
}
|
||||
|
||||
for _, tager := range taggers {
|
||||
for _, x := range tager.GetSumStrings() {
|
||||
h.Write([]byte(x))
|
||||
for _, tagger := range taggers {
|
||||
if tagger == nil {
|
||||
continue
|
||||
}
|
||||
tagger.UpdateEtag(h)
|
||||
}
|
||||
|
||||
w.Header().Set("Etag", strconv.FormatUint(h.Sum64(), 16))
|
||||
@@ -74,6 +75,9 @@ func (s *Server) WriteLastModified(w http.ResponseWriter, r *http.Request, moder
|
||||
|
||||
mtimes := []time.Time{configs.BuildTime()}
|
||||
for _, m := range moders {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
mtimes = append(mtimes, m.GetLastModified()...)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -75,9 +77,9 @@ func (s *Session) AddFlash(typ, msg string) {
|
||||
s.Payload.Flashes = append(s.Payload.Flashes, FlashMessage{typ, msg})
|
||||
}
|
||||
|
||||
// GetSumStrings implements Etager interface.
|
||||
func (s *Session) GetSumStrings() []string {
|
||||
return []string{strconv.FormatInt(s.Payload.LastUpdate.Unix(), 10)}
|
||||
// UpdateEtag implements [server.Etagger] interface.
|
||||
func (s *Session) UpdateEtag(h hash.Hash) {
|
||||
io.WriteString(h, strconv.FormatInt(s.Payload.LastUpdate.UTC().UnixNano(), 10))
|
||||
}
|
||||
|
||||
// GetLastModified implement LastModer interface.
|
||||
|
||||
Reference in New Issue
Block a user