Adding upstream version 2.52.6.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
a960158181
commit
6d002e9543
441 changed files with 95392 additions and 0 deletions
252
middleware/cache/cache.go
vendored
Normal file
252
middleware/cache/cache.go
vendored
Normal file
|
@ -0,0 +1,252 @@
|
|||
// Special thanks to @codemicro for moving this to fiber core
|
||||
// Original middleware: github.com/codemicro/fiber-cache
|
||||
package cache
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/utils"
|
||||
)
|
||||
|
||||
// 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"
|
||||
)
|
||||
|
||||
// directives
|
||||
const (
|
||||
noCache = "no-cache"
|
||||
noStore = "no-store"
|
||||
)
|
||||
|
||||
var ignoreHeaders = map[string]interface{}{
|
||||
"Connection": nil,
|
||||
"Keep-Alive": nil,
|
||||
"Proxy-Authenticate": nil,
|
||||
"Proxy-Authorization": nil,
|
||||
"TE": nil,
|
||||
"Trailers": nil,
|
||||
"Transfer-Encoding": nil,
|
||||
"Upgrade": nil,
|
||||
"Content-Type": nil, // already stored explicitly by the cache manager
|
||||
"Content-Encoding": nil, // already stored explicitly by the cache manager
|
||||
}
|
||||
|
||||
// New creates a new middleware handler
|
||||
func New(config ...Config) fiber.Handler {
|
||||
// Set default config
|
||||
cfg := configDefault(config...)
|
||||
|
||||
// Nothing to cache
|
||||
if int(cfg.Expiration.Seconds()) < 0 {
|
||||
return func(c *fiber.Ctx) error {
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// Cache settings
|
||||
mux = &sync.RWMutex{}
|
||||
timestamp = uint64(time.Now().Unix())
|
||||
)
|
||||
// Create manager to simplify storage operations ( see manager.go )
|
||||
manager := newManager(cfg.Storage)
|
||||
// Create indexed heap for tracking expirations ( see heap.go )
|
||||
heap := &indexedHeap{}
|
||||
// count stored bytes (sizes of response bodies)
|
||||
var storedBytes uint
|
||||
|
||||
// Update timestamp in the configured interval
|
||||
go func() {
|
||||
for {
|
||||
atomic.StoreUint64(×tamp, uint64(time.Now().Unix()))
|
||||
time.Sleep(timestampUpdatePeriod)
|
||||
}
|
||||
}()
|
||||
|
||||
// Delete key from both manager and storage
|
||||
deleteKey := func(dkey string) {
|
||||
manager.del(dkey)
|
||||
// External storage saves body data with different key
|
||||
if cfg.Storage != nil {
|
||||
manager.del(dkey + "_body")
|
||||
}
|
||||
}
|
||||
|
||||
// Return new handler
|
||||
return func(c *fiber.Ctx) error {
|
||||
// Refrain from caching
|
||||
if hasRequestDirective(c, noStore) {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// Only cache selected methods
|
||||
var isExists bool
|
||||
for _, method := range cfg.Methods {
|
||||
if c.Method() == method {
|
||||
isExists = true
|
||||
}
|
||||
}
|
||||
|
||||
if !isExists {
|
||||
c.Set(cfg.CacheHeader, cacheUnreachable)
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// Get key from request
|
||||
// TODO(allocation optimization): try to minimize the allocation from 2 to 1
|
||||
key := cfg.KeyGenerator(c) + "_" + c.Method()
|
||||
|
||||
// Get entry from pool
|
||||
e := manager.get(key)
|
||||
|
||||
// Lock entry
|
||||
mux.Lock()
|
||||
|
||||
// Get timestamp
|
||||
ts := atomic.LoadUint64(×tamp)
|
||||
|
||||
// Check if entry is expired
|
||||
if e.exp != 0 && ts >= e.exp {
|
||||
deleteKey(key)
|
||||
if cfg.MaxBytes > 0 {
|
||||
_, size := heap.remove(e.heapidx)
|
||||
storedBytes -= size
|
||||
}
|
||||
} else if e.exp != 0 && !hasRequestDirective(c, noCache) {
|
||||
// Separate body value to avoid msgp serialization
|
||||
// We can store raw bytes with Storage 👍
|
||||
if cfg.Storage != nil {
|
||||
e.body = manager.getRaw(key + "_body")
|
||||
}
|
||||
// Set response headers from cache
|
||||
c.Response().SetBodyRaw(e.body)
|
||||
c.Response().SetStatusCode(e.status)
|
||||
c.Response().Header.SetContentTypeBytes(e.ctype)
|
||||
if len(e.cencoding) > 0 {
|
||||
c.Response().Header.SetBytesV(fiber.HeaderContentEncoding, e.cencoding)
|
||||
}
|
||||
for k, v := range e.headers {
|
||||
c.Response().Header.SetBytesV(k, v)
|
||||
}
|
||||
// Set Cache-Control header if enabled
|
||||
if cfg.CacheControl {
|
||||
maxAge := strconv.FormatUint(e.exp-ts, 10)
|
||||
c.Set(fiber.HeaderCacheControl, "public, max-age="+maxAge)
|
||||
}
|
||||
|
||||
c.Set(cfg.CacheHeader, cacheHit)
|
||||
|
||||
mux.Unlock()
|
||||
|
||||
// Return response
|
||||
return nil
|
||||
}
|
||||
|
||||
// make sure we're not blocking concurrent requests - do unlock
|
||||
mux.Unlock()
|
||||
|
||||
// Continue stack, return err to Fiber if exist
|
||||
if err := c.Next(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// lock entry back and unlock on finish
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
|
||||
// Don't cache response if Next returns true
|
||||
if cfg.Next != nil && cfg.Next(c) {
|
||||
c.Set(cfg.CacheHeader, cacheUnreachable)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Don't try to cache if body won't fit into cache
|
||||
bodySize := uint(len(c.Response().Body()))
|
||||
if cfg.MaxBytes > 0 && bodySize > cfg.MaxBytes {
|
||||
c.Set(cfg.CacheHeader, cacheUnreachable)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove oldest to make room for new
|
||||
if cfg.MaxBytes > 0 {
|
||||
for storedBytes+bodySize > cfg.MaxBytes {
|
||||
key, size := heap.removeFirst()
|
||||
deleteKey(key)
|
||||
storedBytes -= size
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
|
||||
// Store all response headers
|
||||
// (more: https://datatracker.ietf.org/doc/html/rfc2616#section-13.5.1)
|
||||
if cfg.StoreResponseHeaders {
|
||||
e.headers = make(map[string][]byte)
|
||||
c.Response().Header.VisitAll(
|
||||
func(key, value []byte) {
|
||||
// create real copy
|
||||
keyS := string(key)
|
||||
if _, ok := ignoreHeaders[keyS]; !ok {
|
||||
e.headers[keyS] = utils.CopyBytes(value)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// default cache expiration
|
||||
expiration := cfg.Expiration
|
||||
// Calculate expiration by response header or other setting
|
||||
if cfg.ExpirationGenerator != nil {
|
||||
expiration = cfg.ExpirationGenerator(c, &cfg)
|
||||
}
|
||||
e.exp = ts + uint64(expiration.Seconds())
|
||||
|
||||
// Store entry in heap
|
||||
if cfg.MaxBytes > 0 {
|
||||
e.heapidx = heap.put(key, e.exp, bodySize)
|
||||
storedBytes += bodySize
|
||||
}
|
||||
|
||||
// For external Storage we store raw body separated
|
||||
if cfg.Storage != nil {
|
||||
manager.setRaw(key+"_body", e.body, expiration)
|
||||
// avoid body msgp encoding
|
||||
e.body = nil
|
||||
manager.set(key, e, expiration)
|
||||
manager.release(e)
|
||||
} else {
|
||||
// Store entry in memory
|
||||
manager.set(key, e, expiration)
|
||||
}
|
||||
|
||||
c.Set(cfg.CacheHeader, cacheMiss)
|
||||
|
||||
// Finish response
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check if request has directive
|
||||
func hasRequestDirective(c *fiber.Ctx, directive string) bool {
|
||||
return strings.Contains(c.Get(fiber.HeaderCacheControl), directive)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue