GoBlog/cache.go

124 lines
3.0 KiB
Go
Raw Normal View History

2020-07-29 20:45:26 +00:00
package main
import (
"net/http"
"net/http/httptest"
2020-09-22 14:42:36 +00:00
"sync"
2020-07-29 20:45:26 +00:00
"time"
"golang.org/x/sync/singleflight"
2020-07-29 20:45:26 +00:00
)
var (
cacheGroup singleflight.Group
cacheMap = map[string]*cacheItem{}
cacheMutex = &sync.RWMutex{}
)
2020-09-22 14:42:36 +00:00
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()
}
}()
}
2020-09-22 14:42:36 +00:00
2020-07-30 19:18:13 +00:00
func cacheMiddleware(next http.Handler) http.Handler {
2020-07-29 20:45:26 +00:00
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2020-08-04 17:42:09 +00:00
if appConfig.Cache.Enable &&
// check method
2020-10-20 16:15:15 +00:00
(r.Method == http.MethodGet || r.Method == http.MethodHead) &&
// check bypass query
!(r.URL.Query().Get("cache") == "0") {
key := cacheKey(r)
// Get cache or render it
cacheInterface, _, _ := cacheGroup.Do(key, func() (interface{}, error) {
return getCache(key, next, r), nil
})
cache := cacheInterface.(*cacheItem)
// log.Println(string(cache.body))
cacheTimeString := time.Unix(cache.creationTime, 0).Format(time.RFC1123)
expiresTimeString := time.Unix(cache.creationTime+appConfig.Cache.Expiration, 0).Format(time.RFC1123)
2020-07-30 19:18:13 +00:00
// check conditional request
ifModifiedSinceHeader := r.Header.Get("If-Modified-Since")
if ifModifiedSinceHeader != "" && ifModifiedSinceHeader == cacheTimeString {
2020-07-30 19:08:41 +00:00
setCacheHeaders(w, cacheTimeString, expiresTimeString)
2020-07-30 19:18:13 +00:00
// send 304
w.WriteHeader(http.StatusNotModified)
2020-07-30 19:08:41 +00:00
return
2020-07-29 20:45:26 +00:00
}
2020-07-30 19:18:13 +00:00
// copy cached headers
for k, v := range cache.header {
2020-07-30 19:18:13 +00:00
w.Header()[k] = v
}
setCacheHeaders(w, cacheTimeString, expiresTimeString)
// set status code
w.WriteHeader(cache.code)
2020-07-30 19:18:13 +00:00
// write cached body
_, _ = w.Write(cache.body)
2020-07-30 19:08:41 +00:00
return
2020-07-29 20:45:26 +00:00
}
2020-07-30 19:18:13 +00:00
next.ServeHTTP(w, r)
return
2020-07-29 20:45:26 +00:00
})
}
func cacheKey(r *http.Request) string {
2020-10-20 16:15:15 +00:00
return r.URL.String()
}
2020-07-30 19:08:41 +00:00
func setCacheHeaders(w http.ResponseWriter, cacheTimeString string, expiresTimeString string) {
w.Header().Set("Cache-Control", "public")
w.Header().Set("Last-Modified", cacheTimeString)
w.Header().Set("Expires", expiresTimeString)
}
type cacheItem struct {
creationTime int64
code int
header http.Header
body []byte
2020-10-07 15:35:52 +00:00
}
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()
item, ok := cacheMap[key]
cacheMutex.RUnlock()
if !ok || item.expired() {
// No cache available
// Record request
recorder := httptest.NewRecorder()
next.ServeHTTP(recorder, r)
// Cache values from recorder
2020-10-20 16:15:15 +00:00
item = &cacheItem{
creationTime: time.Now().Unix(),
code: recorder.Code,
header: recorder.Header(),
body: recorder.Body.Bytes(),
}
// Save cache
cacheMutex.Lock()
cacheMap[key] = item
cacheMutex.Unlock()
}
return item
2020-07-29 20:45:26 +00:00
}
2020-10-06 17:07:48 +00:00
func purgeCache() {
cacheMutex.Lock()
cacheMap = map[string]*cacheItem{}
cacheMutex.Unlock()
}