88 "strings"
99 "time"
1010
11+ "github.com/bradfitz/gomemcache/memcache"
1112 t "github.com/darkweak/souin/configurationtypes"
1213 "github.com/darkweak/souin/pkg/rfc"
1314 "github.com/darkweak/souin/pkg/storage/types"
@@ -21,8 +22,9 @@ var nutsMemcachedInstanceMap = map[string]*nutsdb.DB{}
2122// NutsMemcached provider type
2223type NutsMemcached struct {
2324 * nutsdb.DB
24- stale time.Duration
25- logger * zap.Logger
25+ stale time.Duration
26+ logger * zap.Logger
27+ memcacheClient * memcache.Client
2628}
2729
2830// const (
@@ -75,6 +77,13 @@ func NutsMemcachedConnectionFactory(c t.AbstractConfigurationInterface) (types.S
7577 nutsConfiguration := dc .GetNutsMemcached ()
7678 nutsOptions := nutsdb .DefaultOptions
7779 nutsOptions .Dir = "/tmp/souin-nuts-memcached"
80+
81+ // `HintKeyAndRAMIdxMode` represents ram index (only key) mode.
82+ nutsOptions .EntryIdxMode = nutsdb .HintKeyAndRAMIdxMode
83+ // `HintBPTSparseIdxMode` represents b+ tree sparse index mode.
84+ // Note: this mode was removed after v0.14.0
85+ //nutsOptions.EntryIdxMode = nutsdb.HintBPTSparseIdxMode
86+
7887 if nutsConfiguration .Configuration != nil {
7988 var parsedNuts nutsdb.Options
8089 nutsConfiguration .Configuration = sanitizeProperties (nutsConfiguration .Configuration .(map [string ]interface {}))
@@ -110,9 +119,10 @@ func NutsMemcachedConnectionFactory(c t.AbstractConfigurationInterface) (types.S
110119 }
111120
112121 instance := & NutsMemcached {
113- DB : db ,
114- stale : dc .GetStale (),
115- logger : c .GetLogger (),
122+ DB : db ,
123+ stale : dc .GetStale (),
124+ logger : c .GetLogger (),
125+ memcacheClient : memcache .New ("127.0.0.1:11211" ), // hardcoded for now
116126 }
117127 nutsMemcachedInstanceMap [nutsOptions .Dir ] = instance .DB
118128
@@ -169,13 +179,30 @@ func (provider *NutsMemcached) MapKeys(prefix string) map[string]string {
169179
170180// Get method returns the populated response if exists, empty response then
171181func (provider * NutsMemcached ) Get (key string ) (item []byte ) {
172- _ = provider .DB .View (func (tx * nutsdb.Tx ) error {
173- i , e := tx .Get (bucket , []byte (key ))
174- if i != nil {
182+ // get from nuts
183+ keyFound := false
184+ {
185+ _ = provider .DB .View (func (tx * nutsdb.Tx ) error {
186+ i , e := tx .Get (bucket , []byte (key ))
187+ if i != nil {
188+ // Value is stored in memcached
189+ //item = i.Value
190+ keyFound = true
191+ }
192+ return e
193+ })
194+ }
195+
196+ // get from memcached
197+ if keyFound {
198+ // Reminder: the key must be at most 250 bytes in length
199+ //fmt.Println("memcached GET", key)
200+ i , e := provider .memcacheClient .Get (key )
201+ if e == nil && i != nil {
175202 item = i .Value
176203 }
177- return e
178- })
204+
205+ }
179206
180207 return
181208}
@@ -192,17 +219,29 @@ func (provider *NutsMemcached) Prefix(key string, req *http.Request, validator *
192219 } else {
193220 for _ , entry := range entries {
194221 if varyVoter (key , req , string (entry .Key )) {
195- if res , err := http .ReadResponse (bufio .NewReader (bytes .NewBuffer (entry .Value )), req ); err == nil {
196- rfc .ValidateETag (res , validator )
197- if validator .Matched {
198- provider .logger .Sugar ().Debugf ("The stored key %s matched the current iteration key ETag %+v" , string (entry .Key ), validator )
199- result = res
200- return nil
222+ // TODO: improve this
223+ // store header only in nuts and avoid query to memcached on each vary
224+ // E.g, rfc.ValidateETag on NutsDB header value, retrieve response body later from memcached.
225+
226+ // Reminder: the key must be at most 250 bytes in length
227+ //fmt.Println("memcached PREFIX", key, "GET", string(entry.Key))
228+ i , e := provider .memcacheClient .Get (string (entry .Key ))
229+ if e == nil && i != nil {
230+ res , err := http .ReadResponse (bufio .NewReader (bytes .NewBuffer (i .Value )), req )
231+ if err == nil {
232+ rfc .ValidateETag (res , validator )
233+ if validator .Matched {
234+ provider .logger .Sugar ().Debugf ("The stored key %s matched the current iteration key ETag %+v" , string (entry .Key ), validator )
235+ result = res
236+ return nil
237+ }
238+
239+ provider .logger .Sugar ().Debugf ("The stored key %s didn't match the current iteration key ETag %+v" , string (entry .Key ), validator )
240+ } else {
241+ provider .logger .Sugar ().Errorf ("An error occured while reading response for the key %s: %v" , string (entry .Key ), err )
201242 }
202-
203- provider .logger .Sugar ().Debugf ("The stored key %s didn't match the current iteration key ETag %+v" , string (entry .Key ), validator )
204243 } else {
205- provider .logger .Sugar ().Errorf ("An error occured while reading response for the key %s: %v" , string (entry .Key ), err )
244+ provider .logger .Sugar ().Errorf ("An error occured while reading memcached for the key %s: %v" , string (entry .Key ), err )
206245 }
207246 }
208247 }
@@ -214,26 +253,51 @@ func (provider *NutsMemcached) Prefix(key string, req *http.Request, validator *
214253}
215254
216255// Set method will store the response in Nuts provider
217- func (provider * NutsMemcached ) Set (key string , value []byte , url t.URL , duration time.Duration ) error {
218- if duration == 0 {
219- duration = url .TTL .Duration
256+ func (provider * NutsMemcached ) Set (key string , value []byte , url t.URL , ttl time.Duration ) error {
257+ if ttl == 0 {
258+ ttl = url .TTL .Duration
220259 }
221260
222- err := provider .DB .Update (func (tx * nutsdb.Tx ) error {
223- return tx .Put (bucket , []byte (key ), value , uint32 (duration .Seconds ()))
224- })
261+ // set to nuts (normal TTL)
262+ {
263+ err := provider .DB .Update (func (tx * nutsdb.Tx ) error {
264+ // No value is stored, value is stored in memcached
265+ return tx .Put (bucket , []byte (key ), []byte {}, uint32 (ttl .Seconds ()))
266+ })
225267
226- if err != nil {
227- provider .logger .Sugar ().Errorf ("Impossible to set value into Nuts, %v" , err )
228- return err
268+ if err != nil {
269+ provider .logger .Sugar ().Errorf ("Impossible to set value into Nuts, %v" , err )
270+ return err
271+ }
229272 }
230273
231- err = provider .DB .Update (func (tx * nutsdb.Tx ) error {
232- return tx .Put (bucket , []byte (StalePrefix + key ), value , uint32 ((provider .stale + duration ).Seconds ()))
233- })
274+ // set to nuts (stale TTL)
275+ staleTtl := int32 ((provider .stale + ttl ).Seconds ())
276+ {
277+ err := provider .DB .Update (func (tx * nutsdb.Tx ) error {
278+ // No value is stored, value is stored in memcached
279+ return tx .Put (bucket , []byte (StalePrefix + key ), []byte {}, uint32 (staleTtl ))
280+ })
234281
235- if err != nil {
236- provider .logger .Sugar ().Errorf ("Impossible to set value into Nuts, %v" , err )
282+ if err != nil {
283+ provider .logger .Sugar ().Errorf ("Impossible to set value into Nuts, %v" , err )
284+ }
285+ }
286+
287+ // set to memcached with stale TTL
288+ {
289+ // Reminder: the key must be at most 250 bytes in length
290+ //fmt.Println("memcached SET", key)
291+ err := provider .memcacheClient .Set (
292+ & memcache.Item {
293+ Key : key ,
294+ Value : value ,
295+ Expiration : staleTtl ,
296+ },
297+ )
298+ if err != nil {
299+ provider .logger .Sugar ().Errorf ("Impossible to set value into Memcached, %v" , err )
300+ }
237301 }
238302
239303 return nil
0 commit comments