Add garbage collection to cache and cache redirects

This commit is contained in:
Jan-Lukas Else 2020-10-19 23:33:08 +02:00
parent a3c6ba832e
commit 77f6a53a7e
3 changed files with 40 additions and 14 deletions

View File

@ -9,10 +9,27 @@ import (
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
) )
var cacheMap = map[string]*cacheItem{} var (
var cacheMutex = &sync.RWMutex{} cacheGroup singleflight.Group
cacheMap = map[string]*cacheItem{}
cacheMutex = &sync.RWMutex{}
)
var requestGroup singleflight.Group func initCache() {
go func() {
for {
// GC the entries every 60 seconds
time.Sleep(60 * time.Second)
cacheMutex.Lock()
for key, item := range cacheMap {
if item.expired() {
delete(cacheMap, key)
}
}
cacheMutex.Unlock()
}
}()
}
func cacheMiddleware(next http.Handler) http.Handler { func cacheMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -21,11 +38,10 @@ func cacheMiddleware(next http.Handler) http.Handler {
!(r.URL.Query().Get("cache") == "0") && !(r.URL.Query().Get("cache") == "0") &&
// check method // check method
(r.Method == http.MethodGet || r.Method == http.MethodHead) { (r.Method == http.MethodGet || r.Method == http.MethodHead) {
// Fix path key := cacheKey(r)
path := slashTrimmedPath(r)
// Get cache or render it // Get cache or render it
cacheInterface, _, _ := requestGroup.Do(path, func() (interface{}, error) { cacheInterface, _, _ := cacheGroup.Do(key, func() (interface{}, error) {
return getCache(path, next, r), nil return getCache(key, next, r), nil
}) })
cache := cacheInterface.(*cacheItem) cache := cacheInterface.(*cacheItem)
// log.Println(string(cache.body)) // log.Println(string(cache.body))
@ -55,6 +71,10 @@ func cacheMiddleware(next http.Handler) http.Handler {
}) })
} }
func cacheKey(r *http.Request) string {
return slashTrimmedPath(r)
}
func setCacheHeaders(w http.ResponseWriter, cacheTimeString string, expiresTimeString string) { func setCacheHeaders(w http.ResponseWriter, cacheTimeString string, expiresTimeString string) {
w.Header().Set("Cache-Control", "public") w.Header().Set("Cache-Control", "public")
w.Header().Set("Last-Modified", cacheTimeString) w.Header().Set("Last-Modified", cacheTimeString)
@ -68,16 +88,21 @@ type cacheItem struct {
body []byte body []byte
} }
func getCache(path string, next http.Handler, r *http.Request) *cacheItem { func (c *cacheItem) expired() bool {
return c.creationTime < time.Now().Unix()-appConfig.Cache.Expiration
}
func getCache(key string, next http.Handler, r *http.Request) *cacheItem {
cacheMutex.RLock() cacheMutex.RLock()
item, ok := cacheMap[path] item, ok := cacheMap[key]
cacheMutex.RUnlock() cacheMutex.RUnlock()
if !ok || item.creationTime < time.Now().Unix()-appConfig.Cache.Expiration { if !ok || item.expired() {
item = &cacheItem{}
// No cache available // No cache available
item = &cacheItem{}
// Record request
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
next.ServeHTTP(recorder, r) next.ServeHTTP(recorder, r)
// copy values from recorder // Cache values from recorder
now := time.Now() now := time.Now()
item.creationTime = now.Unix() item.creationTime = now.Unix()
item.code = recorder.Code item.code = recorder.Code
@ -85,7 +110,7 @@ func getCache(path string, next http.Handler, r *http.Request) *cacheItem {
item.body = recorder.Body.Bytes() item.body = recorder.Body.Bytes()
// Save cache // Save cache
cacheMutex.Lock() cacheMutex.Lock()
cacheMap[path] = item cacheMap[key] = item
cacheMutex.Unlock() cacheMutex.Unlock()
} }
return item return item

View File

@ -137,7 +137,7 @@ func buildHandler() (http.Handler, error) {
} }
for _, path := range allRedirectPaths { for _, path := range allRedirectPaths {
if path != "" { if path != "" {
r.With(minifier.Middleware).Get(path, serveRedirect) r.With(cacheMiddleware, minifier.Middleware).Get(path, serveRedirect)
} }
} }

View File

@ -49,6 +49,7 @@ func main() {
log.Fatal(err) log.Fatal(err)
return return
} }
initCache()
// Start cron hooks // Start cron hooks
startHourlyHooks() startHourlyHooks()