Files
remark42/backend/app/rest/cache/cache.go
T
UmputunandGitHub 3520de768d feature/mongo cache (#180)
* 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
2018-07-24 18:43:53 -04:00

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) {}