2020-09-26 11:13:11 +02:00
|
|
|
// Special thanks to @codemicro for moving this to fiber core
|
|
|
|
// Original middleware: github.com/codemicro/fiber-cache
|
2020-09-24 23:15:16 +02:00
|
|
|
package cache
|
|
|
|
|
|
|
|
import (
|
2020-10-01 19:37:34 +02:00
|
|
|
"strconv"
|
2020-09-24 23:15:16 +02:00
|
|
|
"sync"
|
2020-10-28 02:29:47 +01:00
|
|
|
"sync/atomic"
|
2020-09-24 23:15:16 +02:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gofiber/fiber/v2"
|
2020-10-29 06:41:05 +01:00
|
|
|
"github.com/gofiber/fiber/v2/utils"
|
2020-09-24 23:15:16 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// New creates a new middleware handler
|
|
|
|
func New(config ...Config) fiber.Handler {
|
|
|
|
// Set default config
|
2020-11-11 13:54:27 +01:00
|
|
|
cfg := configDefault(config...)
|
2020-09-24 23:15:16 +02:00
|
|
|
|
2020-10-28 02:29:47 +01:00
|
|
|
var (
|
|
|
|
// Cache settings
|
|
|
|
timestamp = uint64(time.Now().Unix())
|
|
|
|
expiration = uint64(cfg.Expiration.Seconds())
|
|
|
|
mux = &sync.RWMutex{}
|
|
|
|
|
|
|
|
// Default store logic (if no Store is provided)
|
|
|
|
entries = make(map[string]entry)
|
|
|
|
)
|
|
|
|
|
|
|
|
// Update timestamp every second
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
atomic.StoreUint64(×tamp, uint64(time.Now().Unix()))
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-10-01 16:53:27 +02:00
|
|
|
// Nothing to cache
|
|
|
|
if int(cfg.Expiration.Seconds()) < 0 {
|
|
|
|
return func(c *fiber.Ctx) error {
|
|
|
|
return c.Next()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-01 19:49:49 +02:00
|
|
|
// Remove expired entries
|
2020-10-29 06:14:02 +01:00
|
|
|
if cfg.defaultStore {
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
// GC the entries every 10 seconds
|
|
|
|
time.Sleep(10 * time.Second)
|
|
|
|
mux.Lock()
|
|
|
|
for k := range entries {
|
|
|
|
if atomic.LoadUint64(×tamp) >= entries[k].exp {
|
|
|
|
delete(entries, k)
|
|
|
|
}
|
2020-09-25 00:42:51 +02:00
|
|
|
}
|
2020-10-29 06:14:02 +01:00
|
|
|
mux.Unlock()
|
2020-09-25 00:42:51 +02:00
|
|
|
}
|
2020-10-29 06:14:02 +01:00
|
|
|
}()
|
|
|
|
}
|
2020-09-24 23:15:16 +02:00
|
|
|
|
|
|
|
// Return new handler
|
|
|
|
return func(c *fiber.Ctx) error {
|
2020-10-01 19:37:34 +02:00
|
|
|
// Don't execute middleware if Next returns true
|
2020-09-24 23:15:16 +02:00
|
|
|
if cfg.Next != nil && cfg.Next(c) {
|
|
|
|
return c.Next()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only cache GET methods
|
|
|
|
if c.Method() != fiber.MethodGet {
|
|
|
|
return c.Next()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get key from request
|
2020-10-31 01:19:33 +00:00
|
|
|
key := cfg.Key(c)
|
2020-09-24 23:15:16 +02:00
|
|
|
|
2020-10-28 02:29:47 +01:00
|
|
|
// Create new entry
|
2020-10-29 06:32:09 +01:00
|
|
|
var entry entry
|
|
|
|
var entryBody []byte
|
2020-10-28 02:29:47 +01:00
|
|
|
|
|
|
|
// Lock entry
|
|
|
|
mux.Lock()
|
|
|
|
defer mux.Unlock()
|
|
|
|
|
|
|
|
// Check if we need to use the default in-memory storage
|
|
|
|
if cfg.defaultStore {
|
|
|
|
entry = entries[key]
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// Load data from store
|
2020-11-11 13:54:27 +01:00
|
|
|
storeEntry, err := cfg.Storage.Get(key)
|
2020-10-28 02:29:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only decode if we found an entry
|
2020-10-31 07:51:44 +01:00
|
|
|
if storeEntry != nil {
|
2020-10-28 02:29:47 +01:00
|
|
|
// Decode bytes using msgp
|
|
|
|
if _, err := entry.UnmarshalMsg(storeEntry); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2020-10-29 06:32:09 +01:00
|
|
|
|
2020-11-11 13:54:27 +01:00
|
|
|
if entryBody, err = cfg.Storage.Get(key + "_body"); err != nil {
|
2020-10-29 06:32:09 +01:00
|
|
|
return err
|
|
|
|
}
|
2020-10-28 02:29:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get timestamp
|
|
|
|
ts := atomic.LoadUint64(×tamp)
|
|
|
|
|
|
|
|
// Set expiration if entry does not exist
|
|
|
|
if entry.exp == 0 {
|
|
|
|
entry.exp = ts + expiration
|
|
|
|
|
|
|
|
} else if ts >= entry.exp {
|
2020-10-01 16:53:27 +02:00
|
|
|
// Check if entry is expired
|
2020-10-28 02:29:47 +01:00
|
|
|
// Use default memory storage
|
|
|
|
if cfg.defaultStore {
|
|
|
|
delete(entries, key)
|
|
|
|
} else { // Use custom storage
|
2020-11-11 13:54:27 +01:00
|
|
|
if err := cfg.Storage.Delete(key); err != nil {
|
2020-10-28 02:29:47 +01:00
|
|
|
return err
|
2020-10-01 19:37:34 +02:00
|
|
|
}
|
2020-11-11 13:54:27 +01:00
|
|
|
if err := cfg.Storage.Delete(key + "_body"); err != nil {
|
2020-10-29 06:32:09 +01:00
|
|
|
return err
|
|
|
|
}
|
2020-10-01 16:53:27 +02:00
|
|
|
}
|
2020-10-28 02:29:47 +01:00
|
|
|
|
|
|
|
} else {
|
2020-10-29 06:38:41 +01:00
|
|
|
if cfg.defaultStore {
|
|
|
|
c.Response().SetBodyRaw(entry.body)
|
|
|
|
} else {
|
|
|
|
c.Response().SetBodyRaw(entryBody)
|
|
|
|
}
|
2020-10-28 02:29:47 +01:00
|
|
|
// Set response headers from cache
|
|
|
|
c.Response().SetStatusCode(entry.status)
|
|
|
|
c.Response().Header.SetContentTypeBytes(entry.cType)
|
|
|
|
|
|
|
|
// Set Cache-Control header if enabled
|
|
|
|
if cfg.CacheControl {
|
|
|
|
maxAge := strconv.FormatUint(entry.exp-ts, 10)
|
|
|
|
c.Set(fiber.HeaderCacheControl, "public, max-age="+maxAge)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return response
|
|
|
|
return nil
|
2020-09-24 23:15:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Continue stack, return err to Fiber if exist
|
|
|
|
if err := c.Next(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cache response
|
2020-10-29 06:41:05 +01:00
|
|
|
entryBody = utils.SafeBytes(c.Response().Body())
|
2020-10-28 02:29:47 +01:00
|
|
|
entry.status = c.Response().StatusCode()
|
2020-10-29 06:43:59 +01:00
|
|
|
entry.cType = utils.SafeBytes(c.Response().Header.ContentType())
|
2020-10-28 02:29:47 +01:00
|
|
|
|
|
|
|
// Use default memory storage
|
|
|
|
if cfg.defaultStore {
|
2020-10-29 06:32:09 +01:00
|
|
|
entry.body = entryBody
|
2020-10-28 02:29:47 +01:00
|
|
|
entries[key] = entry
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// Use custom storage
|
|
|
|
data, err := entry.MarshalMsg(nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pass bytes to Storage
|
2020-11-11 13:54:27 +01:00
|
|
|
if err = cfg.Storage.Set(key, data, cfg.Expiration); err != nil {
|
2020-10-28 02:29:47 +01:00
|
|
|
return err
|
|
|
|
}
|
2020-10-29 06:32:09 +01:00
|
|
|
|
|
|
|
// Pass bytes to Storage
|
2020-11-11 13:54:27 +01:00
|
|
|
if err = cfg.Storage.Set(key+"_body", entryBody, cfg.Expiration); err != nil {
|
2020-10-29 06:32:09 +01:00
|
|
|
return err
|
|
|
|
}
|
2020-09-24 23:15:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Finish response
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|