1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-23 15:03:46 +00:00

148 lines
3.7 KiB
Go
Raw Normal View History

// 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 (
"strconv"
2020-11-23 07:38:42 +01: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
)
// timestampUpdatePeriod is the period which is used to check the cache expiration.
// It should not be too long to provide more or less acceptable expiration error, and in the same
// time it should not be too short to avoid overwhelming of the system
const timestampUpdatePeriod = 300 * time.Millisecond
// cache status
// unreachable: when cache is bypass, or invalid
// hit: cache is served
// miss: do not have cache record
const (
cacheUnreachable = "unreachable"
cacheHit = "hit"
cacheMiss = "miss"
)
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
// Nothing to cache
if int(cfg.Expiration.Seconds()) < 0 {
return func(c *fiber.Ctx) error {
return c.Next()
}
}
2020-10-28 02:29:47 +01:00
var (
// Cache settings
2020-11-23 07:38:42 +01:00
mux = &sync.RWMutex{}
2020-10-28 02:29:47 +01:00
timestamp = uint64(time.Now().Unix())
expiration = uint64(cfg.Expiration.Seconds())
)
2020-11-23 07:38:42 +01:00
// Create manager to simplify storage operations ( see manager.go )
manager := newManager(cfg.Storage)
2020-10-28 02:29:47 +01:00
// Update timestamp every second
go func() {
for {
atomic.StoreUint64(&timestamp, uint64(time.Now().Unix()))
time.Sleep(timestampUpdatePeriod)
2020-10-28 02:29:47 +01:00
}
}()
2020-09-24 23:15:16 +02:00
// Return new handler
return func(c *fiber.Ctx) error {
// Only cache GET methods
if c.Method() != fiber.MethodGet {
c.Set(cfg.CacheHeader, cacheUnreachable)
2020-09-24 23:15:16 +02:00
return c.Next()
}
// Get key from request
key := cfg.KeyGenerator(c)
2020-09-24 23:15:16 +02:00
2020-11-23 07:38:42 +01:00
// Get entry from pool
e := manager.get(key)
// Lock entry and unlock when finished
mux.Lock()
defer mux.Unlock()
2020-10-28 02:29:47 +01:00
// Get timestamp
ts := atomic.LoadUint64(&timestamp)
if e.exp != 0 && ts >= e.exp {
// Check if entry is expired
2020-11-23 07:38:42 +01:00
manager.delete(key)
// External storage saves body data with different key
if cfg.Storage != nil {
manager.delete(key + "_body")
}
} else if e.exp != 0 {
// Separate body value to avoid msgp serialization
2020-11-23 07:38:42 +01:00
// We can store raw bytes with Storage 👍
if cfg.Storage != nil {
e.body = manager.getRaw(key + "_body")
}
2020-10-28 02:29:47 +01:00
// Set response headers from cache
2020-11-23 07:38:42 +01:00
c.Response().SetBodyRaw(e.body)
c.Response().SetStatusCode(e.status)
c.Response().Header.SetContentTypeBytes(e.ctype)
2020-12-06 16:43:24 +01:00
if len(e.cencoding) > 0 {
c.Response().Header.SetBytesV(fiber.HeaderContentEncoding, e.cencoding)
}
2020-10-28 02:29:47 +01:00
// Set Cache-Control header if enabled
if cfg.CacheControl {
maxAge := strconv.FormatUint(e.exp-ts, 10)
2020-10-28 02:29:47 +01:00
c.Set(fiber.HeaderCacheControl, "public, max-age="+maxAge)
}
c.Set(cfg.CacheHeader, cacheHit)
2020-10-28 02:29:47 +01:00
// 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
}
// Don't cache response if Next returns true
if cfg.Next != nil && cfg.Next(c) {
c.Set(cfg.CacheHeader, cacheUnreachable)
return nil
}
2020-09-24 23:15:16 +02:00
// Cache response
e.body = utils.CopyBytes(c.Response().Body())
e.status = c.Response().StatusCode()
e.ctype = utils.CopyBytes(c.Response().Header.ContentType())
e.cencoding = utils.CopyBytes(c.Response().Header.Peek(fiber.HeaderContentEncoding))
e.exp = ts + expiration
2020-11-23 07:38:42 +01:00
// For external Storage we store raw body separated
2020-11-23 07:38:42 +01:00
if cfg.Storage != nil {
manager.setRaw(key+"_body", e.body, cfg.Expiration)
// avoid body msgp encoding
e.body = nil
manager.set(key, e, cfg.Expiration)
manager.release(e)
} else {
// Store entry in memory
manager.set(key, e, cfg.Expiration)
}
2020-09-24 23:15:16 +02:00
c.Set(cfg.CacheHeader, cacheMiss)
2020-09-24 23:15:16 +02:00
// Finish response
return nil
}
}