Skip to content

Commit ab38615

Browse files
authored
feat: Replace VaryLayerStorage to plain storage system with algorithm (#93)
* feat: Replace VaryLayerStorage to plain storage system with algorithm * Remove caddy update * Fix coalescing + vary mannagement * Use plain concatenation instead of Sprintf method for varied key
1 parent 64790e7 commit ab38615

16 files changed

+125
-63
lines changed

cache/coalescing/requestCoalescing.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import (
1212

1313
// Temporise will run one call to proxy then use the response for other requests that couldn't reach cached response
1414
func (r *RequestCoalescing) Temporise(req *http.Request, rw http.ResponseWriter, nextMiddleware func(http.ResponseWriter, *http.Request) error) {
15-
ch := r.requestGroup.DoChan(rfc.GetCacheKey(req), func() (interface{}, error) {
15+
key := rfc.GetCacheKey(req)
16+
ch := r.requestGroup.DoChan(key, func() (interface{}, error) {
17+
defer r.requestGroup.Forget(key)
1618
e := nextMiddleware(rw, req)
1719

1820
return nil, e

cache/providers/abstractProvider.go

+28
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ package providers
33
import (
44
"github.com/darkweak/souin/cache/types"
55
"github.com/darkweak/souin/configurationtypes"
6+
"net/http"
7+
"strings"
68
)
79

10+
const VarySeparator = "{-VARY-}"
11+
812
// InitializeProvider allow to generate the providers array according to the configuration
913
func InitializeProvider(configuration configurationtypes.AbstractConfigurationInterface) types.AbstractProviderInterface {
1014
var r types.AbstractProviderInterface
@@ -23,3 +27,27 @@ func InitializeProvider(configuration configurationtypes.AbstractConfigurationIn
2327
}
2428
return r
2529
}
30+
31+
func varyVoter(baseKey string, req *http.Request, currentKey string) bool {
32+
if currentKey == baseKey {
33+
return true
34+
}
35+
36+
if strings.Contains(currentKey, VarySeparator) {
37+
list := currentKey[(strings.LastIndex(currentKey, VarySeparator) + len(VarySeparator)):]
38+
if len(list) == 0 {
39+
return false
40+
}
41+
42+
for _, item := range strings.Split(list, ";") {
43+
index := strings.LastIndex(item, ":")
44+
if req.Header.Get(item[:index]) != item[index+1:] {
45+
return false
46+
}
47+
}
48+
49+
return true
50+
}
51+
52+
return false
53+
}

cache/providers/badgerProvider.go

+23
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
t "github.com/darkweak/souin/configurationtypes"
66
badger "github.com/dgraph-io/badger/v3"
7+
"net/http"
78
"regexp"
89
"time"
910
)
@@ -65,6 +66,28 @@ func (provider *Badger) Get(key string) []byte {
6566
return result
6667
}
6768

69+
// Prefix method returns the populated response if exists, empty response then
70+
func (provider *Badger) Prefix(key string, req *http.Request) []byte {
71+
var result []byte
72+
73+
_ = provider.DB.View(func(txn *badger.Txn) error {
74+
prefix := []byte(key)
75+
it := txn.NewIterator(badger.DefaultIteratorOptions)
76+
defer it.Close()
77+
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
78+
if varyVoter(key, req, string(it.Item().Key())) {
79+
_ = it.Item().Value(func(val []byte) error {
80+
result = val
81+
return nil
82+
})
83+
}
84+
}
85+
return nil
86+
})
87+
88+
return result
89+
}
90+
6891
// Set method will store the response in Badger provider
6992
func (provider *Badger) Set(key string, value []byte, url t.URL, duration time.Duration) {
7093
if duration == 0 {

cache/providers/embeddedOlricProvider.go

+28
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"go.uber.org/zap"
1212
"gopkg.in/yaml.v3"
1313
"io/ioutil"
14+
"net/http"
1415
"os"
1516
"time"
1617
)
@@ -123,6 +124,33 @@ func (provider *EmbeddedOlric) ListKeys() []string {
123124
return keys
124125
}
125126

127+
// Prefix method returns the populated response if exists, empty response then
128+
func (provider *EmbeddedOlric) Prefix(key string, req *http.Request) []byte {
129+
c, err := provider.dm.Query(query.M{
130+
"$onKey": query.M{
131+
"$regexMatch": "^" + key,
132+
},
133+
})
134+
135+
if err != nil {
136+
fmt.Println(fmt.Sprintf("An error occurred while trying to retrieve data in Olric: %s", err))
137+
return []byte{}
138+
}
139+
defer c.Close()
140+
141+
res := []byte{}
142+
err = c.Range(func(k string, v interface{}) bool {
143+
if varyVoter(key, req, k) {
144+
res = v.([]byte)
145+
return false
146+
}
147+
148+
return true
149+
})
150+
151+
return res
152+
}
153+
126154
// Get method returns the populated response if exists, empty response then
127155
func (provider *EmbeddedOlric) Get(key string) []byte {
128156
val2, err := provider.dm.Get(key)

cache/providers/olricProvider.go

+28
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/buraksezer/olric/config"
77
"github.com/buraksezer/olric/query"
88
t "github.com/darkweak/souin/configurationtypes"
9+
"net/http"
910
"time"
1011
)
1112

@@ -62,6 +63,33 @@ func (provider *Olric) ListKeys() []string {
6263
return keys
6364
}
6465

66+
// Prefix method returns the populated response if exists, empty response then
67+
func (provider *Olric) Prefix(key string, req *http.Request) []byte {
68+
c, err := provider.dm.Query(query.M{
69+
"$onKey": query.M{
70+
"$regexMatch": "^" + key,
71+
},
72+
})
73+
74+
if err != nil {
75+
fmt.Println(fmt.Sprintf("An error occurred while trying to retrieve data in Olric: %s", err))
76+
return []byte{}
77+
}
78+
defer c.Close()
79+
80+
res := []byte{}
81+
err = c.Range(func(k string, v interface{}) bool {
82+
if varyVoter(key, req, k) {
83+
res = v.([]byte)
84+
return false
85+
}
86+
87+
return true
88+
})
89+
90+
return res
91+
}
92+
6593
// Get method returns the populated response if exists, empty response then
6694
func (provider *Olric) Get(key string) []byte {
6795
val2, err := provider.dm.Get(key)

cache/types/layerStorage.go

-33
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,6 @@ import (
44
"github.com/dgraph-io/ristretto"
55
)
66

7-
// VaryLayerStorage is the layer for Vary support storage
8-
type VaryLayerStorage struct {
9-
*ristretto.Cache
10-
}
11-
12-
// InitializeVaryLayerStorage initialize the storage
13-
func InitializeVaryLayerStorage() *VaryLayerStorage {
14-
storage, _ := ristretto.NewCache(&ristretto.Config{
15-
NumCounters: 1e7, // number of keys to track frequency of (10M).
16-
MaxCost: 1 << 30, // maximum cost of cache (1GB).
17-
BufferItems: 64, // number of keys per Get buffer.
18-
})
19-
20-
return &VaryLayerStorage{Cache: storage}
21-
}
22-
23-
// Get method returns the varied headers list if exists, empty array then
24-
func (provider *VaryLayerStorage) Get(key string) []string {
25-
val, found := provider.Cache.Get(key)
26-
if !found {
27-
return []string{}
28-
}
29-
return val.([]string)
30-
}
31-
32-
// Set method will store the response in Ristretto provider
33-
func (provider *VaryLayerStorage) Set(key string, headers []string) {
34-
isSet := provider.Cache.Set(key, headers, 1)
35-
if !isSet {
36-
panic("Impossible to set value into Ristretto")
37-
}
38-
}
39-
407
// CoalescingLayerStorage is the layer to be able to not coalesce uncoalesceable request
418
type CoalescingLayerStorage struct {
429
*ristretto.Cache

cache/types/providers.go

+2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package types
22

33
import (
44
"github.com/darkweak/souin/configurationtypes"
5+
"net/http"
56
"time"
67
)
78

89
// AbstractProviderInterface should be implemented in any providers
910
type AbstractProviderInterface interface {
1011
ListKeys() []string
12+
Prefix(key string, req *http.Request) []byte
1113
Get(key string) []byte
1214
Set(key string, value []byte, url configurationtypes.URL, duration time.Duration)
1315
Delete(key string)

cache/types/souin.go

-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ type TransportInterface interface {
1313
RoundTrip(req *http.Request) (resp *http.Response, err error)
1414
SetURL(url configurationtypes.URL)
1515
UpdateCacheEventually(req *http.Request) (resp *http.Response, err error)
16-
GetVaryLayerStorage() *VaryLayerStorage
1716
GetCoalescingLayerStorage() *CoalescingLayerStorage
1817
GetYkeyStorage() *ykeys.YKeyStorage
1918
}
@@ -28,7 +27,6 @@ type Transport struct {
2827
Provider AbstractProviderInterface
2928
ConfigurationURL configurationtypes.URL
3029
MarkCachedResponses bool
31-
VaryLayerStorage *VaryLayerStorage
3230
CoalescingLayerStorage *CoalescingLayerStorage
3331
YkeyStorage *ykeys.YKeyStorage
3432
}

plugins/base.go

-5
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ func DefaultSouinPluginCallback(
2727

2828
go func() {
2929
cacheKey := rfc.GetCacheKey(req)
30-
varied := retriever.GetTransport().GetVaryLayerStorage().Get(cacheKey)
31-
if len(varied) != 0 {
32-
cacheKey = rfc.GetVariedCacheKey(req, varied)
33-
}
3430
go func() {
3531
coalesceable <- retriever.GetTransport().GetCoalescingLayerStorage().Exists(cacheKey)
3632
}()
@@ -69,7 +65,6 @@ func DefaultSouinPluginCallback(
6965
} else {
7066
_ = nextMiddleware(res, req)
7167
}
72-
close(coalesceable)
7368
}
7469

7570
// DefaultSouinPluginInitializerFromConfiguration is the default initialization for plugins

plugins/caddy/app.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/caddyserver/caddy/v2"
55
)
66

7+
// SouinApp contains the whole Souin necessary items
78
type SouinApp struct {
89
*DefaultCache
910
LogLevel string `json:"log_level,omitempty"`
@@ -32,7 +33,7 @@ func (s SouinApp) Stop() error {
3233
}
3334

3435
// CaddyModule implements caddy.ModuleInfo
35-
func (a SouinApp) CaddyModule() caddy.ModuleInfo {
36+
func (s SouinApp) CaddyModule() caddy.ModuleInfo {
3637
return caddy.ModuleInfo{
3738
ID: moduleName,
3839
New: func() caddy.Module { return new(SouinApp) },

plugins/caddy/go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
150150
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
151151
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
152152
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
153+
github.com/darkweak/souin v1.5.2 h1:WS/Q+qq7c+dIIw/abYYtWMpfj1ucjhfvaabW5Q+K6tc=
154+
github.com/darkweak/souin v1.5.2/go.mod h1:7TNt0JUJaB3Aq7wP0L45XQhwJJbKw5zMmfp6OTZTFMA=
153155
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
154156
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
155157
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

plugins/caddy/main.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ type SouinCaddyPlugin struct {
3737
Configuration *Configuration
3838
logger *zap.Logger
3939
LogLevel string `json:"log_level,omitempty"`
40-
bufPool sync.Pool
40+
bufPool *sync.Pool
4141
Headers []string `json:"headers,omitempty"`
4242
Olric configurationtypes.CacheProvider `json:"olric,omitempty"`
4343
TTL string `json:"ttl,omitempty"`
44-
ykeys map[string]configurationtypes.YKey `json:"ykeys,omitempty"`
44+
YKeys map[string]configurationtypes.YKey `json:"ykeys,omitempty"`
4545
}
4646

4747
// CaddyModule returns the Caddy module information.
@@ -105,6 +105,7 @@ func (s *SouinCaddyPlugin) configurationPropertyMapper() error {
105105
LogLevel: s.LogLevel,
106106
}
107107
}
108+
s.Configuration.Ykeys = s.YKeys
108109
s.Configuration.DefaultCache = defaultCache
109110
return nil
110111
}
@@ -163,7 +164,7 @@ func (s *SouinCaddyPlugin) Provision(ctx caddy.Context) error {
163164
return err
164165
}
165166

166-
s.bufPool = sync.Pool{
167+
s.bufPool = &sync.Pool{
167168
New: func() interface{} {
168169
return new(bytes.Buffer)
169170
},

rfc/bridge.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const (
1717
// otherwise.
1818
func CachedResponse(c types.AbstractProviderInterface, req *http.Request, cachedKey string, transport types.TransportInterface, update bool) (types.ReverseResponse, error) {
1919
clonedReq := cloneRequest(req)
20-
cachedVal := c.Get(cachedKey)
20+
cachedVal := c.Prefix(cachedKey, req)
2121
b := bytes.NewBuffer(cachedVal)
2222
response, _ := http.ReadResponse(bufio.NewReader(b), clonedReq)
2323
if update && nil != response && ValidateCacheControl(response) {
@@ -61,10 +61,6 @@ func (t *VaryTransport) BaseRoundTrip(req *http.Request, shouldReUpdate bool) (s
6161
cacheable := IsVaryCacheable(req)
6262
cachedResp := req.Response
6363
if cacheable {
64-
varied := t.GetVaryLayerStorage().Get(cacheKey)
65-
if len(varied) != 0 {
66-
cacheKey = GetVariedCacheKey(req, varied)
67-
}
6864
cr, _ := CachedResponse(t.GetProvider(), req, cacheKey, t, shouldReUpdate)
6965
if cr.Response != nil {
7066
cachedResp = cr.Response

rfc/standalone.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"bytes"
66
"errors"
77
"fmt"
8+
"github.com/darkweak/souin/cache/providers"
89
"io"
910
"net/http"
1011
"strings"
@@ -18,11 +19,10 @@ func GetCacheKey(req *http.Request) string {
1819

1920
// GetVariedCacheKey returns the varied cache key for req and resp.
2021
func GetVariedCacheKey(req *http.Request, headers []string) string {
21-
str := ""
22-
for _, v := range headers {
23-
str += fmt.Sprintf("%s:%s", v, req.Header.Get(v))
22+
for i, v := range headers {
23+
headers[i] = fmt.Sprintf("%s:%s", v, req.Header.Get(v))
2424
}
25-
return fmt.Sprintf("%s-[%s]", GetCacheKey(req), strings.Join(headers[:], ";"))
25+
return GetCacheKey(req) + providers.VarySeparator + strings.Join(headers, ";")
2626
}
2727

2828
// getFreshness will return one of fresh/stale/transparent based on the cache-control

rfc/transport.go

-6
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ func IsVaryCacheable(req *http.Request) bool {
2424
func NewTransport(p types.AbstractProviderInterface, ykeyStorage *ykeys.YKeyStorage) *VaryTransport {
2525
return &VaryTransport{
2626
Provider: p,
27-
VaryLayerStorage: types.InitializeVaryLayerStorage(),
2827
CoalescingLayerStorage: types.InitializeCoalescingLayerStorage(),
2928
MarkCachedResponses: true,
3029
YkeyStorage: ykeyStorage,
@@ -41,11 +40,6 @@ func (t *VaryTransport) SetURL(url configurationtypes.URL) {
4140
t.ConfigurationURL = url
4241
}
4342

44-
// GetVaryLayerStorage get the vary layer storagecache/coalescing/requestCoalescing_test.go
45-
func (t *VaryTransport) GetVaryLayerStorage() *types.VaryLayerStorage {
46-
return t.VaryLayerStorage
47-
}
48-
4943
// GetCoalescingLayerStorage get the coalescing layer storage
5044
func (t *VaryTransport) GetCoalescingLayerStorage() *types.CoalescingLayerStorage {
5145
return t.CoalescingLayerStorage

rfc/vary.go

-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ func validateVary(req *http.Request, resp *http.Response, key string, t *VaryTra
2323
variedHeaders := headerAllCommaSepValues(resp.Header, "vary")
2424
cacheKey := key
2525
if len(variedHeaders) > 0 {
26-
go func() {
27-
t.VaryLayerStorage.Set(key, variedHeaders)
28-
}()
2926
cacheKey = GetVariedCacheKey(req, variedHeaders)
3027
}
3128
switch req.Method {

0 commit comments

Comments
 (0)