* add siteID to cache Get * indirect option setters * add mongo cache with tests, add Key and Flusher * lint: minor warns * workaround for cache parallel test * repeater in mongo cache * missing repeater vendor * fix nop cache * add cache mongo benchmark * wired mongo cache, single opts group for mongo * disable goconst * stop cache repeated on not found error * use local mongo for tests in travis
113 lines
2.6 KiB
Go
113 lines
2.6 KiB
Go
package cache
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/umputun/remark/backend/app/rest"
|
|
)
|
|
|
|
// LoadingCache defines interface for caching
|
|
type LoadingCache interface {
|
|
Get(key Key, fn func() ([]byte, error)) (data []byte, err error)
|
|
Flush(req FlusherRequest)
|
|
}
|
|
|
|
type cacheWithOpts interface {
|
|
LoadingCache
|
|
setMaxValSize(max int) error
|
|
setMaxKeys(max int) error
|
|
setMaxCacheSize(max int64) error
|
|
setPostFlushFn(postFlushFn func()) error
|
|
}
|
|
|
|
// Key for cache
|
|
type Key struct {
|
|
id string
|
|
siteID string
|
|
scopes []string
|
|
}
|
|
|
|
// NewKey makes keys for site
|
|
func NewKey(site string) Key {
|
|
res := Key{siteID: site}
|
|
return res
|
|
}
|
|
|
|
// ID sets key id
|
|
func (k Key) ID(id string) Key {
|
|
k.id = id
|
|
return k
|
|
}
|
|
|
|
// Scopes of the key
|
|
func (k Key) Scopes(scopes ...string) Key {
|
|
k.scopes = scopes
|
|
return k
|
|
}
|
|
|
|
// Merge makes full string key from primary key and scopes
|
|
func (k Key) Merge() string {
|
|
return strings.Join(k.scopes, "$$") + "@@" + k.id + "@@" + k.siteID
|
|
}
|
|
|
|
// ParseKey gets compound key created by Key func and split it to the actual key and scopes
|
|
func ParseKey(fullKey string) (Key, error) {
|
|
elems := strings.Split(fullKey, "@@")
|
|
if len(elems) != 3 {
|
|
return Key{}, errors.Errorf("can't parse cache key %s", fullKey)
|
|
}
|
|
scopes := strings.Split(elems[0], "$$")
|
|
if len(scopes) == 1 && scopes[0] == "" {
|
|
scopes = []string{}
|
|
}
|
|
key := Key{
|
|
scopes: scopes,
|
|
id: elems[1],
|
|
siteID: elems[2],
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
// FlusherRequest used as input for cache.Flush
|
|
type FlusherRequest struct {
|
|
siteID string
|
|
scopes []string
|
|
}
|
|
|
|
// Flusher makes new FlusherRequest with empty scopes
|
|
func Flusher(siteID string) FlusherRequest {
|
|
res := FlusherRequest{siteID: siteID}
|
|
return res
|
|
}
|
|
|
|
// Scopes adds scopes to FlusherRequest
|
|
func (f FlusherRequest) Scopes(scopes ...string) FlusherRequest {
|
|
f.scopes = scopes
|
|
return f
|
|
}
|
|
|
|
// URLKey gets url from request to use it as cache key
|
|
// admins will have different keys in order to prevent leak of admin-only data to regular users
|
|
func URLKey(r *http.Request) string {
|
|
adminPrefix := "admin!!"
|
|
key := strings.TrimPrefix(r.URL.String(), adminPrefix) // prevents attach with fake url to get admin view
|
|
if user, err := rest.GetUserInfo(r); err == nil && user.Admin { // make separate cache key for admins
|
|
key = adminPrefix + key
|
|
}
|
|
return key
|
|
}
|
|
|
|
// Nop does nothing for caching, passing fn call only
|
|
type Nop struct{}
|
|
|
|
// Get calls fn, no actual caching
|
|
func (n *Nop) Get(key Key, fn func() ([]byte, error)) (data []byte, err error) {
|
|
return fn()
|
|
}
|
|
|
|
// Flush does nothing for NoopCache
|
|
func (n *Nop) Flush(req FlusherRequest) {}
|