mirror of https://github.com/jlelse/GoBlog
Disable cache for static and media files, serve template assets directly from memory
This commit is contained in:
parent
5343826ec9
commit
abe1f39ea4
26
cache.go
26
cache.go
|
@ -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,
|
||||||
|
|
6
http.go
6
http.go
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue