Disable cache for static and media files, serve template assets directly from memory

This commit is contained in:
Jan-Lukas Else 2021-01-10 17:19:08 +01:00
parent 5343826ec9
commit abe1f39ea4
4 changed files with 47 additions and 31 deletions

View File

@ -49,7 +49,7 @@ func cacheMiddleware(next http.Handler) http.Handler {
} }
setCacheHeaders(w, cache) setCacheHeaders(w, cache)
// check conditional request // check conditional request
if ifNoneMatchHeader := r.Header.Get("If-None-Match"); ifNoneMatchHeader != "" && ifNoneMatchHeader == cache.hash { if ifNoneMatchHeader := r.Header.Get("If-None-Match"); ifNoneMatchHeader != "" && ifNoneMatchHeader == cache.eTag {
// send 304 // send 304
w.WriteHeader(http.StatusNotModified) w.WriteHeader(http.StatusNotModified)
return return
@ -78,8 +78,7 @@ func cacheKey(r *http.Request) string {
} }
func setCacheHeaders(w http.ResponseWriter, cache *cacheItem) { func setCacheHeaders(w http.ResponseWriter, cache *cacheItem) {
w.Header().Del(cacheInternalExpirationHeader) w.Header().Set("ETag", cache.eTag)
w.Header().Set("ETag", cache.hash)
w.Header().Set("Last-Modified", cache.creationTime.UTC().Format(http.TimeFormat)) w.Header().Set("Last-Modified", cache.creationTime.UTC().Format(http.TimeFormat))
if w.Header().Get("Cache-Control") == "" { if w.Header().Get("Cache-Control") == "" {
if cache.expiration != 0 { if cache.expiration != 0 {
@ -93,7 +92,7 @@ func setCacheHeaders(w http.ResponseWriter, cache *cacheItem) {
type cacheItem struct { type cacheItem struct {
expiration int expiration int
creationTime time.Time creationTime time.Time
hash string eTag string
code int code int
header http.Header header http.Header
body []byte body []byte
@ -126,20 +125,29 @@ func getCache(key string, next http.Handler, r *http.Request) (item *cacheItem)
result := recorder.Result() result := recorder.Result()
body, _ := ioutil.ReadAll(result.Body) body, _ := ioutil.ReadAll(result.Body)
_ = result.Body.Close() _ = result.Body.Close()
h := sha256.New() eTag := result.Header.Get("ETag")
_, _ = io.Copy(h, bytes.NewReader(body)) if eTag == "" {
hash := fmt.Sprintf("%x", h.Sum(nil)) h := sha256.New()
exp, _ := strconv.Atoi(result.Header.Get(cacheInternalExpirationHeader)) _, _ = io.Copy(h, bytes.NewReader(body))
eTag = fmt.Sprintf("%x", h.Sum(nil))
}
lastMod := time.Now() lastMod := time.Now()
if lm := result.Header.Get("Last-Modified"); lm != "" { if lm := result.Header.Get("Last-Modified"); lm != "" {
if parsedTime, te := dateparse.ParseLocal(lm); te == nil { if parsedTime, te := dateparse.ParseLocal(lm); te == nil {
lastMod = parsedTime lastMod = parsedTime
} }
} }
exp, _ := strconv.Atoi(result.Header.Get(cacheInternalExpirationHeader))
// Remove problematic headers
result.Header.Del(cacheInternalExpirationHeader)
result.Header.Del("Accept-Ranges")
result.Header.Del("ETag")
result.Header.Del("Last-Modified")
// Create cache item
item = &cacheItem{ item = &cacheItem{
expiration: exp, expiration: exp,
creationTime: lastMod, creationTime: lastMod,
hash: hash, eTag: eTag,
code: result.StatusCode, code: result.StatusCode,
header: result.Header, header: result.Header,
body: body, body: body,

View File

@ -172,16 +172,16 @@ func buildHandler() (http.Handler, error) {
// Assets // Assets
for _, path := range allAssetPaths() { for _, path := range allAssetPaths() {
r.With(cacheMiddleware).Get(path, serveAsset) r.Get(path, serveAsset)
} }
// Static files // Static files
for _, path := range allStaticPaths() { for _, path := range allStaticPaths() {
r.With(cacheMiddleware).Get(path, serveStaticFile) r.Get(path, serveStaticFile)
} }
// Media files // Media files
r.With(cacheMiddleware).Get(`/m/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`, serveMediaFile) r.Get(`/m/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`, serveMediaFile)
// Short paths // Short paths
r.With(cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", redirectToLongPath) r.With(cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", redirectToLongPath)

View File

@ -130,7 +130,7 @@ func initRendering() error {
} }
return d.Before(b) return d.Before(b)
}, },
"asset": assetFile, "asset": assetFileName,
"string": getTemplateStringVariant, "string": getTemplateStringVariant,
"include": func(templateName string, data ...interface{}) (template.HTML, error) { "include": func(templateName string, data ...interface{}) (template.HTML, error) {
if len(data) == 1 { if len(data) == 1 {

View File

@ -4,6 +4,7 @@ import (
"crypto/sha1" "crypto/sha1"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"mime"
"net/http" "net/http"
"os" "os"
"path" "path"
@ -13,23 +14,23 @@ import (
const assetsFolder = "templates/assets" const assetsFolder = "templates/assets"
var compiledAssetsFolder string var assetFileNames map[string]string = map[string]string{}
var assetFiles map[string]string var assetFiles map[string]*assetFile = map[string]*assetFile{}
type assetFile struct {
contentType string
body []byte
}
func initTemplateAssets() (err error) { func initTemplateAssets() (err error) {
compiledAssetsFolder, err = ioutil.TempDir("", "goblog-assets-*")
if err != nil {
return
}
assetFiles = map[string]string{}
err = filepath.Walk(assetsFolder, func(path string, info os.FileInfo, err error) error { err = filepath.Walk(assetsFolder, func(path string, info os.FileInfo, err error) error {
if info.Mode().IsRegular() { if info.Mode().IsRegular() {
compiled, err := compileAssets(path) compiled, err := compileAsset(path)
if err != nil { if err != nil {
return err return err
} }
if compiled != "" { if compiled != "" {
assetFiles[strings.TrimPrefix(path, assetsFolder+"/")] = compiled assetFileNames[strings.TrimPrefix(path, assetsFolder+"/")] = compiled
} }
} }
return nil return nil
@ -40,7 +41,7 @@ func initTemplateAssets() (err error) {
return nil return nil
} }
func compileAssets(name string) (compiledFileName string, err error) { func compileAsset(name string) (compiledFileName string, err error) {
originalContent, err := ioutil.ReadFile(name) originalContent, err := ioutil.ReadFile(name)
if err != nil { if err != nil {
return return
@ -67,21 +68,21 @@ func compileAssets(name string) (compiledFileName string, err error) {
sha.Write(compiledContent) sha.Write(compiledContent)
hash := fmt.Sprintf("%x", sha.Sum(nil)) hash := fmt.Sprintf("%x", sha.Sum(nil))
compiledFileName = hash + compiledExt compiledFileName = hash + compiledExt
err = ioutil.WriteFile(path.Join(compiledAssetsFolder, compiledFileName), compiledContent, 0644) assetFiles[compiledFileName] = &assetFile{
if err != nil { contentType: mime.TypeByExtension(compiledExt),
return body: compiledContent,
} }
return return
} }
// Function for templates // Function for templates
func assetFile(fileName string) string { func assetFileName(fileName string) string {
return "/" + assetFiles[fileName] return "/" + assetFileNames[fileName]
} }
func allAssetPaths() []string { func allAssetPaths() []string {
var paths []string var paths []string
for _, name := range assetFiles { for _, name := range assetFileNames {
paths = append(paths, "/"+name) paths = append(paths, "/"+name)
} }
return paths return paths
@ -89,6 +90,13 @@ func allAssetPaths() []string {
// Gets only called by registered paths // Gets only called by registered paths
func serveAsset(w http.ResponseWriter, r *http.Request) { func serveAsset(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Cache-Control", "public,max-age=31536000,immutable") f := strings.TrimPrefix(r.URL.Path, "/")
http.ServeFile(w, r, filepath.Join(compiledAssetsFolder, r.URL.Path)) af, ok := assetFiles[f]
if !ok {
serve404(w, r)
return
}
w.Header().Set("Cache-Control", "public,max-age=31536000,immutable")
w.Header().Set(contentType, af.contentType)
w.Write(af.body)
} }