mirror of
https://github.com/caddyserver/caddy.git
synced 2025-03-22 13:29:27 +01:00
adding basic cache middleware, currently no cache size limit.
This commit is contained in:
parent
60beddf6c8
commit
8a7063bfa2
2 changed files with 136 additions and 0 deletions
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/mholt/caddy/middleware"
|
||||
"github.com/mholt/caddy/middleware/basicauth"
|
||||
"github.com/mholt/caddy/middleware/browse"
|
||||
"github.com/mholt/caddy/middleware/cache"
|
||||
"github.com/mholt/caddy/middleware/errors"
|
||||
"github.com/mholt/caddy/middleware/extensions"
|
||||
"github.com/mholt/caddy/middleware/fastcgi"
|
||||
|
@ -41,6 +42,7 @@ import (
|
|||
func init() {
|
||||
register("log", log.New)
|
||||
register("gzip", gzip.New)
|
||||
register("cache", cache.New)
|
||||
register("errors", errors.New)
|
||||
register("header", headers.New)
|
||||
register("rewrite", rewrite.New)
|
||||
|
|
134
middleware/cache/cache.go
vendored
Normal file
134
middleware/cache/cache.go
vendored
Normal file
|
@ -0,0 +1,134 @@
|
|||
// Package cache provides a simple middleware layer that remembers
|
||||
// previously served requests and serves those from memory.
|
||||
package cache
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/mholt/caddy/middleware"
|
||||
)
|
||||
|
||||
// Example line in CaddyFile: cache 60 128mb 10mb
|
||||
|
||||
// Cache is an http.Handler that can remembers and sends back stored responses
|
||||
type Cache struct {
|
||||
Next middleware.Handler
|
||||
Lifetime int
|
||||
Entries map[string]CacheEntry // url -> entry
|
||||
Rule Rule
|
||||
}
|
||||
|
||||
type CacheEntry struct {
|
||||
created int64
|
||||
lastUsed int64
|
||||
Size int //bytes
|
||||
Code int
|
||||
HeaderMap http.Header
|
||||
Body []byte
|
||||
}
|
||||
|
||||
type Rule struct {
|
||||
MaxAge int64 //seconds
|
||||
MaxCacheSize int64 //bytes
|
||||
MaxCacheEntrySize int //bytes
|
||||
}
|
||||
|
||||
// New creates a new cache middleware instance.
|
||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
||||
rules, err := parse(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(next middleware.Handler) middleware.Handler {
|
||||
// TODO: handle more than one rule? handle first or last rule?
|
||||
return Cache{Next: next, Lifetime: 60 * 10, Entries: make(map[string]CacheEntry), Rule: rules[0]}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parse(c middleware.Controller) ([]Rule, error) {
|
||||
var rules []Rule
|
||||
|
||||
for c.Next() {
|
||||
|
||||
var ageString, cacheSizeString, cacheEntrySizeString string
|
||||
if !c.Args(&ageString, &cacheSizeString, &cacheEntrySizeString) {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
age, err := strconv.Atoi(ageString)
|
||||
if err != nil {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
cacheSize, err := humanize.ParseBytes(cacheSizeString)
|
||||
if err != nil {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
cacheEntrySize, err := humanize.ParseBytes(cacheEntrySizeString)
|
||||
if err != nil {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
rule := Rule{MaxAge: int64(age), MaxCacheSize: int64(cacheSize), MaxCacheEntrySize: int(cacheEntrySize)}
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
// Writes the headers and body to the writer
|
||||
func WriteEntry(w http.ResponseWriter, entry CacheEntry) {
|
||||
w.WriteHeader(entry.Code)
|
||||
for key, valueArray := range entry.HeaderMap {
|
||||
for _, value := range valueArray {
|
||||
w.Header().Set(key, value)
|
||||
}
|
||||
}
|
||||
w.Write(entry.Body)
|
||||
}
|
||||
|
||||
func ClientAllowsCaching(r *http.Request) bool {
|
||||
// TODO: Actually parse the Cache-Control and Pragma header
|
||||
// Currently this won't do any caching if these headers are present
|
||||
return r.Header.Get("Cache-Control") == "" && r.Header.Get("Pragma") == ""
|
||||
}
|
||||
|
||||
func ServerAllowsCaching(headers http.Header) bool {
|
||||
// TODO: This is more strict than necessary. Better parsing needed.
|
||||
cacheControl := headers.Get("Cache-Control")
|
||||
return cacheControl == "" || cacheControl == "public"
|
||||
}
|
||||
|
||||
// ServeHTTP serves a gzipped response if the client supports it.
|
||||
func (c Cache) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if r.Method == "GET" {
|
||||
key := r.RequestURI
|
||||
now := time.Now().Unix()
|
||||
entry, inCache := c.Entries[key]
|
||||
|
||||
if inCache && now-entry.created < c.Rule.MaxAge && ClientAllowsCaching(r) {
|
||||
entry.lastUsed = time.Now().Unix()
|
||||
} else {
|
||||
record := httptest.NewRecorder()
|
||||
status, err := c.Next.ServeHTTP(record, r)
|
||||
normalResponse := err == nil && status < 300 && record.Code < 300
|
||||
|
||||
body := record.Body.Bytes()
|
||||
bodySize := len(body) + 100 // TODO: Better approximation of size of headers, etc. For now just 100 bytes.
|
||||
entry = CacheEntry{created: now, lastUsed: now, Code: record.Code, HeaderMap: record.HeaderMap, Body: body, Size: bodySize}
|
||||
|
||||
if normalResponse && bodySize < c.Rule.MaxCacheEntrySize && ServerAllowsCaching(record.Header()) {
|
||||
// adds response to cache
|
||||
c.Entries[key] = entry
|
||||
}
|
||||
}
|
||||
|
||||
WriteEntry(w, entry)
|
||||
return entry.Code, nil
|
||||
} else {
|
||||
// skip caching entirely
|
||||
return c.Next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue