mirror of
https://github.com/foomo/neosproxy.git
synced 2025-10-16 12:35:39 +00:00
feat: etag cache fingerprinting (#4)
* feat: etag cache fingerprinting * fix: logrus sirupsen package dependency
This commit is contained in:
parent
279978eb89
commit
d36eb1837e
4
cache/content/constructor.go
vendored
4
cache/content/constructor.go
vendored
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/foomo/neosproxy/cache/content/store"
|
||||
"github.com/foomo/neosproxy/client/cms"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
// New will return a newly created content cache
|
||||
@ -15,7 +16,8 @@ func New(cacheLifetime time.Duration, store store.CacheStore, loader cms.Content
|
||||
store: store,
|
||||
|
||||
// invalidationChannel: make(chan InvalidationRequest),
|
||||
lifetime: cacheLifetime,
|
||||
invalidationRequestGroup: &singleflight.Group{},
|
||||
lifetime: cacheLifetime,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
8
cache/content/getter.go
vendored
8
cache/content/getter.go
vendored
@ -26,3 +26,11 @@ func (c *Cache) Len() int {
|
||||
}
|
||||
return counter
|
||||
}
|
||||
|
||||
func (c *Cache) GetAllEtags(workspace string) (etags map[string]string) {
|
||||
return c.store.GetAllEtags(workspace)
|
||||
}
|
||||
|
||||
func (c *Cache) GetEtag(hash string) (etag string, e error) {
|
||||
return c.store.GetEtag(hash)
|
||||
}
|
||||
|
||||
22
cache/content/invalidator.go
vendored
22
cache/content/invalidator.go
vendored
@ -1,6 +1,7 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/neosproxy/cache/content/store"
|
||||
@ -11,8 +12,7 @@ func (c *Cache) RemoveAll() (err error) {
|
||||
return c.store.RemoveAll()
|
||||
}
|
||||
|
||||
// Invalidate cache item
|
||||
func (c *Cache) Invalidate(id, dimension, workspace string) (item store.CacheItem, err error) {
|
||||
func (c *Cache) invalidator(id, dimension, workspace string) (item store.CacheItem, err error) {
|
||||
|
||||
// timer
|
||||
start := time.Now()
|
||||
@ -40,7 +40,23 @@ func (c *Cache) Invalidate(id, dimension, workspace string) (item store.CacheIte
|
||||
Duration: time.Since(start),
|
||||
})
|
||||
|
||||
// done
|
||||
return
|
||||
}
|
||||
|
||||
// Invalidate cache item
|
||||
func (c *Cache) Invalidate(id, dimension, workspace string) (item store.CacheItem, err error) {
|
||||
|
||||
groupName := strings.Join([]string{"invalidate", id, dimension, workspace}, "-")
|
||||
itemInterfaced, errThrottled, _ := c.invalidationRequestGroup.Do(groupName, func() (i interface{}, e error) {
|
||||
return c.invalidator(id, dimension, workspace)
|
||||
})
|
||||
|
||||
if errThrottled != nil {
|
||||
err = errThrottled
|
||||
return
|
||||
}
|
||||
|
||||
item = itemInterfaced.(store.CacheItem)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
3
cache/content/store/cache.go
vendored
3
cache/content/store/cache.go
vendored
@ -7,6 +7,9 @@ type CacheStore interface {
|
||||
Get(hash string) (item CacheItem, e error)
|
||||
GetAll() (item []CacheItem, e error)
|
||||
|
||||
GetEtag(hash string) (etag string, e error)
|
||||
GetAllEtags(workspace string) (etags map[string]string)
|
||||
|
||||
Count() (int, error)
|
||||
|
||||
Remove(hash string) (e error)
|
||||
|
||||
128
cache/content/store/fs/filesystem.go
vendored
128
cache/content/store/fs/filesystem.go
vendored
@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/neosproxy/cache/content"
|
||||
"github.com/foomo/neosproxy/cache/content/store"
|
||||
@ -23,6 +24,9 @@ type fsCacheStore struct {
|
||||
lock sync.Mutex
|
||||
rw map[string]*sync.RWMutex
|
||||
l logging.Entry
|
||||
|
||||
lockEtags sync.RWMutex
|
||||
etags map[string]string
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
@ -38,14 +42,21 @@ func NewCacheStore(cacheDir string) store.CacheStore {
|
||||
l.WithError(err).Fatal("failed creating cache directory")
|
||||
}
|
||||
|
||||
s := &fsCacheStore{
|
||||
f := &fsCacheStore{
|
||||
CacheDir: cacheDir,
|
||||
lock: sync.Mutex{},
|
||||
rw: make(map[string]*sync.RWMutex),
|
||||
l: l,
|
||||
|
||||
l: l,
|
||||
|
||||
lock: sync.Mutex{},
|
||||
rw: make(map[string]*sync.RWMutex),
|
||||
|
||||
lockEtags: sync.RWMutex{},
|
||||
etags: make(map[string]string),
|
||||
}
|
||||
|
||||
return s
|
||||
go f.initEtagCache()
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
@ -56,6 +67,11 @@ func (f *fsCacheStore) Upsert(item store.CacheItem) (e error) {
|
||||
// key
|
||||
key := f.getItemKey(item)
|
||||
|
||||
// validate etag
|
||||
if item.Etag == "" {
|
||||
item.Etag = item.GetEtag()
|
||||
}
|
||||
|
||||
// serialize
|
||||
bytes, errMarshall := json.Marshal(item)
|
||||
if errMarshall != nil {
|
||||
@ -64,10 +80,54 @@ func (f *fsCacheStore) Upsert(item store.CacheItem) (e error) {
|
||||
|
||||
// lock
|
||||
cacheFile := f.Lock(key)
|
||||
defer f.Unlock(key)
|
||||
|
||||
// write to file
|
||||
return ioutil.WriteFile(cacheFile, bytes, 0644)
|
||||
errWrite := ioutil.WriteFile(cacheFile, bytes, 0644)
|
||||
if errWrite != nil {
|
||||
f.Unlock(key)
|
||||
e = errWrite
|
||||
return
|
||||
}
|
||||
f.Unlock(key)
|
||||
|
||||
// update etag
|
||||
f.upsertEtag(item.Hash, item.Etag)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fsCacheStore) GetAllEtags(workspace string) (etags map[string]string) {
|
||||
f.lockEtags.RLock()
|
||||
etags = make(map[string]string)
|
||||
for hash, etag := range f.etags {
|
||||
if !strings.HasPrefix(hash, workspace) {
|
||||
continue
|
||||
}
|
||||
etags[hash] = etag
|
||||
}
|
||||
f.lockEtags.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *fsCacheStore) GetEtag(hash string) (etag string, e error) {
|
||||
f.lockEtags.RLock()
|
||||
if value, ok := f.etags[hash]; ok {
|
||||
etag = value
|
||||
f.lockEtags.RUnlock()
|
||||
return
|
||||
}
|
||||
f.lockEtags.RUnlock()
|
||||
|
||||
item, errGet := f.Get(hash)
|
||||
if errGet != nil {
|
||||
e = errGet
|
||||
return
|
||||
}
|
||||
|
||||
etag = item.GetEtag()
|
||||
f.upsertEtag(hash, etag)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (f *fsCacheStore) Get(hash string) (item store.CacheItem, e error) {
|
||||
@ -146,7 +206,17 @@ func (f *fsCacheStore) Remove(hash string) (e error) {
|
||||
cacheFile := f.Lock(key)
|
||||
defer f.Unlock(key)
|
||||
|
||||
return os.Remove(cacheFile)
|
||||
errRemove := os.Remove(cacheFile)
|
||||
if errRemove != nil {
|
||||
e = errRemove
|
||||
return
|
||||
}
|
||||
|
||||
f.lockEtags.Lock()
|
||||
delete(f.etags, hash)
|
||||
f.lockEtags.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fsCacheStore) createCacheDir() error {
|
||||
@ -163,6 +233,10 @@ func (f *fsCacheStore) RemoveAll() (e error) {
|
||||
return errRemoveAll
|
||||
}
|
||||
|
||||
f.lockEtags.Lock()
|
||||
f.etags = make(map[string]string)
|
||||
f.lockEtags.Unlock()
|
||||
|
||||
errCreateCache := f.createCacheDir()
|
||||
if errCreateCache != nil {
|
||||
f.l.WithError(errCreateCache).Error("unable to re-create cache directory")
|
||||
@ -176,6 +250,44 @@ func (f *fsCacheStore) RemoveAll() (e error) {
|
||||
// ~ PRIVATE METHODS
|
||||
//------------------------------------------------------------------
|
||||
|
||||
func (f *fsCacheStore) initEtagCache() {
|
||||
start := time.Now()
|
||||
l := f.l.WithField(logging.FieldFunction, "initEtagCache")
|
||||
files, errReadDir := ioutil.ReadDir(f.CacheDir)
|
||||
if errReadDir != nil {
|
||||
l.WithError(errReadDir).Error("failed reading cache dir")
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug("init etag cache")
|
||||
counter := 0
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
filename := file.Name()
|
||||
index := strings.Index(filename, ".")
|
||||
if index >= 0 {
|
||||
filename = filename[0:index]
|
||||
}
|
||||
item, errGet := f.Get(filename)
|
||||
if errGet != nil {
|
||||
l.WithError(errGet).Warn("could not load cache item")
|
||||
continue
|
||||
}
|
||||
counter++
|
||||
f.upsertEtag(item.Hash, item.GetEtag())
|
||||
}
|
||||
}
|
||||
l.WithField("len", counter).WithDuration(start).Debug("etag cache initialized")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (f *fsCacheStore) upsertEtag(hash, etag string) {
|
||||
f.lockEtags.Lock()
|
||||
f.etags[hash] = etag
|
||||
f.lockEtags.Unlock()
|
||||
}
|
||||
|
||||
func (f *fsCacheStore) getItemKey(item store.CacheItem) string {
|
||||
return f.getKey(item.Hash)
|
||||
}
|
||||
|
||||
18
cache/content/store/vo.go
vendored
18
cache/content/store/vo.go
vendored
@ -1,6 +1,8 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@ -19,6 +21,7 @@ type CacheItem struct {
|
||||
validUntil time.Time
|
||||
|
||||
HTML string
|
||||
Etag string // hashed fingerprint of html content
|
||||
}
|
||||
|
||||
// NewCacheItem will create a new cache item
|
||||
@ -31,10 +34,25 @@ func NewCacheItem(id string, dimension string, workspace string, html string, va
|
||||
created: time.Now(),
|
||||
validUntil: validUntil,
|
||||
HTML: html,
|
||||
Etag: generateFingerprint(html),
|
||||
}
|
||||
}
|
||||
|
||||
// GetEtag returns an etag
|
||||
func (item *CacheItem) GetEtag() string {
|
||||
if item.Etag != "" {
|
||||
return item.Etag
|
||||
}
|
||||
return generateFingerprint(item.HTML)
|
||||
}
|
||||
|
||||
// GetHash will return a cache item hash
|
||||
func GetHash(id, dimension, workspace string) string {
|
||||
return strings.Join([]string{workspace, dimension, id}, "_")
|
||||
}
|
||||
|
||||
func generateFingerprint(data string) string {
|
||||
sha := sha256.New()
|
||||
sha.Write([]byte(data))
|
||||
return hex.EncodeToString(sha.Sum(nil))
|
||||
}
|
||||
|
||||
10
cache/content/vo.go
vendored
10
cache/content/vo.go
vendored
@ -5,14 +5,16 @@ import (
|
||||
|
||||
"github.com/foomo/neosproxy/cache/content/store"
|
||||
"github.com/foomo/neosproxy/client/cms"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
// Cache workspace items
|
||||
type Cache struct {
|
||||
observer Observer
|
||||
loader cms.ContentLoader
|
||||
store store.CacheStore
|
||||
invalidationChannel chan InvalidationRequest
|
||||
observer Observer
|
||||
loader cms.ContentLoader
|
||||
store store.CacheStore
|
||||
invalidationChannel chan InvalidationRequest
|
||||
invalidationRequestGroup *singleflight.Group
|
||||
|
||||
lifetime time.Duration // time until an item must be re-invalidated (< 0 === never)
|
||||
}
|
||||
|
||||
2
cache/invalidator.go
vendored
2
cache/invalidator.go
vendored
@ -5,9 +5,9 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/cloudfoundry/bytefmt"
|
||||
"github.com/foomo/neosproxy/logging"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Invalidate cache maybe invalidates cache, but skips requests if invalidation queue is already full
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/foomo/neosproxy/cache/content/store/fs"
|
||||
"github.com/foomo/neosproxy/client/cms"
|
||||
"github.com/foomo/neosproxy/config"
|
||||
|
||||
54
glide.lock
generated
54
glide.lock
generated
@ -1,54 +0,0 @@
|
||||
hash: c8774276cbc27688b992f05c04cf2e330655e27c5a22bc9b3cad5dae8fd66b60
|
||||
updated: 2018-12-24T02:41:18.010901+01:00
|
||||
imports:
|
||||
- name: github.com/auth0/go-jwt-middleware
|
||||
version: 5493cabe49f7bfa6e2ec444a09d334d90cd4e2bd
|
||||
- name: github.com/cloudfoundry/bytefmt
|
||||
version: 2aa6f33b730c79971cfc3c742f279195b0abc627
|
||||
- name: github.com/dgrijalva/jwt-go
|
||||
version: 06ea1031745cb8b3dab3f6a236daf2b0aa468b7e
|
||||
- name: github.com/foomo/shop
|
||||
version: e55de455dd26d50c2033008174da3bff0125e218
|
||||
subpackages:
|
||||
- persistence
|
||||
- name: github.com/gorilla/context
|
||||
version: 51ce91d2eaddeca0ef29a71d766bb3634dadf729
|
||||
- name: github.com/gorilla/mux
|
||||
version: e3702bed27f0d39777b0b37b664b6280e8ef8fbf
|
||||
- name: github.com/konsorten/go-windows-terminal-sequences
|
||||
version: 5c8c8bd35d3832f5d134ae1e1e375b69a4d25242
|
||||
- name: github.com/pkg/errors
|
||||
version: 059132a15dd08d6704c67711dae0cf35ab991756
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: bcd833dfe83d3cebad139e4a29ed79cb2318bf95
|
||||
- name: github.com/stretchr/testify
|
||||
version: f35b8ab0b5a2cef36673838d662e249dd9c94686
|
||||
subpackages:
|
||||
- assert
|
||||
- name: golang.org/x/crypto
|
||||
version: 81e90905daefcd6fd217b62423c0908922eadb30
|
||||
subpackages:
|
||||
- ssh/terminal
|
||||
- name: golang.org/x/sys
|
||||
version: b05ddf57801d2239d6ab0ee35f9d981e0420f4ac
|
||||
subpackages:
|
||||
- unix
|
||||
- windows
|
||||
- name: gopkg.in/mgo.v2
|
||||
version: 9856a29383ce1c59f308dd1cf0363a79b5bef6b5
|
||||
subpackages:
|
||||
- bson
|
||||
- internal/json
|
||||
- internal/sasl
|
||||
- internal/scram
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: 51d6538a90f86fe93ac480b35f37b2be17fef232
|
||||
testImports:
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: d8f796af33cc11cb798c1aaeb27a4ebc5099927d
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: 792786c7400a136282c1664665ae0a8db921c6c2
|
||||
subpackages:
|
||||
- difflib
|
||||
22
glide.yaml
22
glide.yaml
@ -1,22 +0,0 @@
|
||||
package: github.com/foomo/neosproxy
|
||||
import:
|
||||
- package: github.com/Sirupsen/logrus
|
||||
version: ~1.2.0
|
||||
- package: gopkg.in/yaml.v2
|
||||
version: ~2.2.2
|
||||
- package: golang.org/x/crypto
|
||||
subpackages:
|
||||
- ssh/terminal
|
||||
- package: golang.org/x/sys
|
||||
subpackages:
|
||||
- unix
|
||||
- package: github.com/stretchr/testify
|
||||
version: ~1.2.2
|
||||
subpackages:
|
||||
- assert
|
||||
- package: github.com/gorilla/mux
|
||||
version: ~1.6.2
|
||||
- package: github.com/auth0/go-jwt-middleware
|
||||
- package: github.com/dgrijalva/jwt-go
|
||||
version: ~3.2.0
|
||||
- package: github.com/cloudfoundry/bytefmt
|
||||
19
go.mod
Normal file
19
go.mod
Normal file
@ -0,0 +1,19 @@
|
||||
module github.com/foomo/neosproxy
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7
|
||||
github.com/cloudfoundry/bytefmt v0.0.0-20180906201452-2aa6f33b730c
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/foomo/shop v0.0.0-20190306093145-644b0b683ba1
|
||||
github.com/gorilla/mux v1.6.2
|
||||
github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0
|
||||
github.com/sirupsen/logrus v1.2.0
|
||||
github.com/stretchr/testify v1.2.2
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc
|
||||
golang.org/x/sync v0.0.0-20170517211232-f52d1811a629
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
)
|
||||
30
go.sum
Normal file
30
go.sum
Normal file
@ -0,0 +1,30 @@
|
||||
github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7 h1:irR1cO6eek3n5uquIVaRAsQmZnlsfPuHNz31cXo4eyk=
|
||||
github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM=
|
||||
github.com/cloudfoundry/bytefmt v0.0.0-20180906201452-2aa6f33b730c h1:zE9z4EZZwJTjOi9Q9WYM/81BuwOKyjhHagiNUDhDdnI=
|
||||
github.com/cloudfoundry/bytefmt v0.0.0-20180906201452-2aa6f33b730c/go.mod h1:4oo6ExqTPaBVBwSm814h6UO5Fels1kN2KvpNscaCcS0=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/foomo/shop v0.0.0-20190306093145-644b0b683ba1/go.mod h1:+Y2nUdyvktarq9+F2B0tvk/BM7y+jwNJI1CW/KgM7as=
|
||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0 h1:R+lX9nKwNd1n7UE5SQAyoorREvRn3aLF6ZndXBoIWqY=
|
||||
github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc h1:F5tKCVGp+MUAHhKp5MZtGqAlGX3+oCsiL1Q629FL90M=
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/sync v0.0.0-20170517211232-f52d1811a629 h1:wqoYUzeICxRnvJCvfHTh0OY0VQ6xern7nYq+ccc19e4=
|
||||
golang.org/x/sync v0.0.0-20170517211232-f52d1811a629/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20171031081856-95c657629925/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ package logging
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func GetDefaultLogEntry() Entry {
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var Logger *logrus.Logger
|
||||
|
||||
@ -3,7 +3,7 @@ package notifier
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/foomo/neosproxy/cache"
|
||||
"github.com/foomo/neosproxy/logging"
|
||||
|
||||
|
||||
96
proxy/api.go
96
proxy/api.go
@ -7,7 +7,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/foomo/neosproxy/cache/content/store"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/cloudfoundry/bytefmt"
|
||||
"github.com/foomo/neosproxy/cache"
|
||||
"github.com/foomo/neosproxy/client/cms"
|
||||
@ -50,6 +52,17 @@ func (p *Proxy) getContent(w http.ResponseWriter, r *http.Request) {
|
||||
logging.FieldID: id,
|
||||
})
|
||||
|
||||
// etag cache handling
|
||||
headerEtag := r.Header.Get("ETag")
|
||||
if headerEtag != "" {
|
||||
etag, errEtag := p.contentCache.GetEtag(store.GetHash(id, dimension, workspace))
|
||||
if errEtag == nil && etag != "" && etag == headerEtag {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
log.WithDuration(start).Debug("content not modified")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// try cache hit, invalidate in case of item not found
|
||||
item, errCacheGet := p.contentCache.Get(id, dimension, workspace)
|
||||
if errCacheGet != nil {
|
||||
@ -78,6 +91,8 @@ func (p *Proxy) getContent(w http.ResponseWriter, r *http.Request) {
|
||||
HTML: item.HTML,
|
||||
}
|
||||
|
||||
w.Header().Set("ETag", item.GetEtag())
|
||||
|
||||
// stream json response
|
||||
encoder := json.NewEncoder(w)
|
||||
errEncode := encoder.Encode(data)
|
||||
@ -88,7 +103,7 @@ func (p *Proxy) getContent(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// done
|
||||
log.WithDuration(start).Debug("served content")
|
||||
log.WithDuration(start).Debug("content served")
|
||||
return
|
||||
}
|
||||
|
||||
@ -241,12 +256,87 @@ func (p *Proxy) streamStatus(w http.ResponseWriter, r *http.Request) {
|
||||
// error handling
|
||||
if errEncode != nil {
|
||||
log.WithError(errEncode).WithField("content-negotiation", contentNegotioation).Error("failed streaming status")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
http.Error(w, "failed streaming status", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (p *Proxy) getAllEtags(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// extract request data
|
||||
workspace := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("workspace")))
|
||||
|
||||
// validate workspace
|
||||
if workspace == "" {
|
||||
workspace = cms.WorkspaceLive
|
||||
}
|
||||
|
||||
// logger
|
||||
log := p.setupLogger(r, "getAllEtags").WithField(logging.FieldWorkspace, workspace)
|
||||
|
||||
etags := p.contentCache.GetAllEtags(workspace)
|
||||
|
||||
w.Header().Set("Content-Type", string(mimeApplicationJSON))
|
||||
encoder := json.NewEncoder(w)
|
||||
errEncode := encoder.Encode(etags)
|
||||
|
||||
// error handling
|
||||
if errEncode != nil {
|
||||
log.WithError(errEncode).Error("failed encoding etags")
|
||||
http.Error(w, "failed encoding etags", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) getEtag(w http.ResponseWriter, r *http.Request, hash string) {
|
||||
// logger
|
||||
log := p.setupLogger(r, "getEtag").WithField(logging.FieldID, hash)
|
||||
|
||||
etag, errEtag := p.contentCache.GetEtag(hash)
|
||||
|
||||
// error handling
|
||||
if errEtag != nil {
|
||||
log.WithError(errEtag).Error("failed getting etag")
|
||||
http.Error(w, "failed getting etag", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", string(mimeTextPlain))
|
||||
w.Header().Set("ETag", etag)
|
||||
w.Write([]byte(etag))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) getEtagByID(w http.ResponseWriter, r *http.Request) {
|
||||
// extract request data
|
||||
workspace := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("workspace")))
|
||||
|
||||
// validate workspace
|
||||
if workspace == "" {
|
||||
workspace = cms.WorkspaceLive
|
||||
}
|
||||
|
||||
// extract request data
|
||||
id := getRequestParameter(r, "id")
|
||||
dimension := getRequestParameter(r, "dimension")
|
||||
|
||||
hash := store.GetHash(id, dimension, workspace)
|
||||
|
||||
p.getEtag(w, r, hash)
|
||||
}
|
||||
|
||||
func (p *Proxy) getEtagByHash(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// extract request data
|
||||
hash := getRequestParameter(r, "hash")
|
||||
|
||||
p.getEtag(w, r, hash)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Private methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -16,12 +16,25 @@ const routeContentServerExport = "/contentserver/export"
|
||||
func (p *Proxy) setupRoutes() {
|
||||
|
||||
// hijack content server export routes
|
||||
|
||||
// content tree / sitemap
|
||||
p.router.HandleFunc(routeContentServerExport, p.streamCachedNeosContentServerExport)
|
||||
p.router.HandleFunc(routeContentServerExport, p.streamCachedNeosContentServerExport).Queries("workspace", "{workspace}")
|
||||
|
||||
// /contentserver/export/de/571fd1ae-c8e4-4d91-a708-d97025fb015c?workspace=stage
|
||||
p.router.HandleFunc(routeContentServerExport+"/{dimension}/{id}", p.getContent)
|
||||
p.router.HandleFunc(routeContentServerExport+"/{dimension}/{id}", p.getContent).Queries("workspace", "{workspace}")
|
||||
// etag
|
||||
p.router.HandleFunc(routeContentServerExport+"/etag/{dimension}/{id}", p.getEtagByID).Methods(http.MethodGet)
|
||||
p.router.HandleFunc(routeContentServerExport+"/etag/{dimension}/{id}", p.getEtagByID).Methods(http.MethodGet).Queries("workspace", "{workspace}")
|
||||
p.router.HandleFunc(routeContentServerExport+"/etag/{hash}", p.getEtagByHash).Methods(http.MethodGet)
|
||||
|
||||
p.router.HandleFunc(routeContentServerExport+"/etags", p.getAllEtags).Methods(http.MethodGet)
|
||||
p.router.HandleFunc(routeContentServerExport+"/etags", p.getAllEtags).Methods(http.MethodGet).Queries("workspace", "{workspace}")
|
||||
|
||||
// documents => /contentserver/export/de/571fd1ae-c8e4-4d91-a708-d97025fb015c?workspace=stage
|
||||
p.router.HandleFunc(routeContentServerExport+"/{dimension}/{id}", p.getContent).Methods(http.MethodGet)
|
||||
p.router.HandleFunc(routeContentServerExport+"/{dimension}/{id}", p.getContent).Methods(http.MethodGet).Queries("workspace", "{workspace}")
|
||||
|
||||
p.router.HandleFunc(routeContentServerExport+"/{dimension}/{id}", p.getEtagByID).Methods(http.MethodHead)
|
||||
p.router.HandleFunc(routeContentServerExport+"/{dimension}/{id}", p.getEtagByID).Methods(http.MethodHead).Queries("workspace", "{workspace}")
|
||||
|
||||
// api
|
||||
// neosproxy/cache/%s?workspace=%s
|
||||
|
||||
Loading…
Reference in New Issue
Block a user