add cache control for frontend assets
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -338,7 +339,7 @@ func (s *Rest) routes() chi.Router {
|
||||
})
|
||||
|
||||
// file server for static content from /web
|
||||
addFileServer(router, "/web", http.Dir(s.WebRoot))
|
||||
addFileServer(router, "/web", http.Dir(s.WebRoot), s.Version)
|
||||
return router
|
||||
}
|
||||
|
||||
@@ -444,7 +445,7 @@ func (s *Rest) configCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// serves static files from /web or embedded by statik
|
||||
func addFileServer(r chi.Router, path string, root http.FileSystem) {
|
||||
func addFileServer(r chi.Router, path string, root http.FileSystem, version string) {
|
||||
|
||||
var webFS http.Handler
|
||||
|
||||
@@ -466,15 +467,17 @@ func addFileServer(r chi.Router, path string, root http.FileSystem) {
|
||||
}
|
||||
path += "*"
|
||||
|
||||
r.With(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(20, nil)), middleware.Timeout(10*time.Second)).
|
||||
Get(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
// don't show dirs, just serve files
|
||||
if strings.HasSuffix(r.URL.Path, "/") && len(r.URL.Path) > 1 && r.URL.Path != (origPath+"/") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
webFS.ServeHTTP(w, r)
|
||||
})
|
||||
r.With(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(20, nil)),
|
||||
middleware.Timeout(10*time.Second),
|
||||
cacheControl(time.Hour*24, version),
|
||||
).Get(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
// don't show dirs, just serve files
|
||||
if strings.HasSuffix(r.URL.Path, "/") && len(r.URL.Path) > 1 && r.URL.Path != (origPath+"/") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
webFS.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func encodeJSONWithHTML(v interface{}) ([]byte, error) {
|
||||
@@ -566,6 +569,32 @@ func matchSiteID(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
// cacheControl is a middleware setting cache expiration. Using url+version as etag
|
||||
func cacheControl(expiration time.Duration, version string) func(http.Handler) http.Handler {
|
||||
|
||||
etag := func(r *http.Request, version string) string {
|
||||
s := version + ":" + r.URL.String()
|
||||
return store.EncodeID(s)
|
||||
}
|
||||
|
||||
return func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
e := `"` + etag(r, version) + `"`
|
||||
w.Header().Set("Etag", e)
|
||||
w.Header().Set("Cache-Control", "max-age="+strconv.Itoa(int(expiration.Seconds())))
|
||||
|
||||
if match := r.Header.Get("If-None-Match"); match != "" {
|
||||
if strings.Contains(match, e) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
func parseError(err error, defaultCode int) (code int) {
|
||||
code = defaultCode
|
||||
|
||||
|
||||
@@ -293,6 +293,39 @@ func TestRest_parseError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRest_cacheControl(t *testing.T) {
|
||||
|
||||
tbl := []struct {
|
||||
url string
|
||||
version string
|
||||
exp time.Duration
|
||||
etag string
|
||||
maxAge int
|
||||
}{
|
||||
{"http://example.com/foo", "v1", time.Hour, "b433be1ea19edaee9dc92ca4b895b6bdf3c058cb", 3600},
|
||||
{"http://example.com/foo2", "v1", 10 * time.Hour, "6d8466aef3246c1057452561acddf7ad9d0d99e0", 36000},
|
||||
{"http://example.com/foo", "v2", time.Hour, "481700c52aab0dfbca99f3ffc2a4fbb27884c114", 3600},
|
||||
{"https://example.com/foo", "v2", time.Hour, "bebd4f1b87f474792c4e75e5affe31fbf67f5778", 3600},
|
||||
}
|
||||
|
||||
for i, tt := range tbl {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", tt.url, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
h := cacheControl(tt.exp, tt.version)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
h.ServeHTTP(w, req)
|
||||
resp := w.Result()
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
t.Logf("%+v", resp.Header)
|
||||
assert.Equal(t, `"`+tt.etag+`"`, resp.Header.Get("Etag"))
|
||||
assert.Equal(t, `max-age=`+strconv.Itoa(int(tt.exp.Seconds())), resp.Header.Get("Cache-Control"))
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func startupT(t *testing.T) (ts *httptest.Server, srv *Rest, teardown func()) {
|
||||
log.Setup(log.CallerFile, log.CallerFunc, log.Msec, log.LevelBraces)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user