This commit is contained in:
@@ -1,18 +1,38 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"vctp/version"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"vctp/version"
|
||||
)
|
||||
|
||||
// CacheMiddleware sets the Cache-Control header based on the version.
|
||||
func CacheMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if version.Value == "dev" {
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("Expires", "0")
|
||||
} else {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000")
|
||||
cacheControl := "public, max-age=31536000"
|
||||
if isVersionedAssetRequest(r) {
|
||||
cacheControl += ", immutable"
|
||||
}
|
||||
w.Header().Set("Cache-Control", cacheControl)
|
||||
w.Header().Set("Expires", time.Now().UTC().Add(365*24*time.Hour).Format(http.TimeFormat))
|
||||
}
|
||||
w.Header().Set("Vary", "Accept-Encoding")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func isVersionedAssetRequest(r *http.Request) bool {
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
if r.URL.Query().Get("v") != "" {
|
||||
return true
|
||||
}
|
||||
return strings.Contains(r.URL.Path, "@")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"vctp/version"
|
||||
)
|
||||
|
||||
func TestCacheMiddlewareDev(t *testing.T) {
|
||||
orig := version.Value
|
||||
version.Value = "dev"
|
||||
defer func() { version.Value = orig }()
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/assets/css/web3.css", nil)
|
||||
h := CacheMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
h.ServeHTTP(rr, req)
|
||||
|
||||
if got := rr.Header().Get("Cache-Control"); got != "no-cache, no-store, must-revalidate" {
|
||||
t.Fatalf("unexpected Cache-Control: %q", got)
|
||||
}
|
||||
if got := rr.Header().Get("Pragma"); got != "no-cache" {
|
||||
t.Fatalf("unexpected Pragma: %q", got)
|
||||
}
|
||||
if got := rr.Header().Get("Expires"); got != "0" {
|
||||
t.Fatalf("unexpected Expires: %q", got)
|
||||
}
|
||||
if got := rr.Header().Get("Vary"); got != "Accept-Encoding" {
|
||||
t.Fatalf("unexpected Vary: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheMiddlewareProd(t *testing.T) {
|
||||
orig := version.Value
|
||||
version.Value = "1.2.3"
|
||||
defer func() { version.Value = orig }()
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/assets/css/web3.css?v=1.2.3", nil)
|
||||
h := CacheMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
h.ServeHTTP(rr, req)
|
||||
|
||||
if got := rr.Header().Get("Cache-Control"); got != "public, max-age=31536000, immutable" {
|
||||
t.Fatalf("unexpected Cache-Control: %q", got)
|
||||
}
|
||||
if rr.Header().Get("Expires") == "" {
|
||||
t.Fatalf("expected Expires header")
|
||||
}
|
||||
if got := rr.Header().Get("Vary"); got != "Accept-Encoding" {
|
||||
t.Fatalf("unexpected Vary: %q", got)
|
||||
}
|
||||
if got := rr.Header().Get("Pragma"); got != "" {
|
||||
t.Fatalf("expected no Pragma in prod, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheMiddlewareProdUnversionedStillCached(t *testing.T) {
|
||||
orig := version.Value
|
||||
version.Value = "1.2.3"
|
||||
defer func() { version.Value = orig }()
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/swagger/swagger-ui.css", nil)
|
||||
h := CacheMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
h.ServeHTTP(rr, req)
|
||||
|
||||
if got := rr.Header().Get("Cache-Control"); got != "public, max-age=31536000" {
|
||||
t.Fatalf("unexpected Cache-Control: %q", got)
|
||||
}
|
||||
if rr.Header().Get("Expires") == "" {
|
||||
t.Fatalf("expected Expires header")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"vctp/version"
|
||||
)
|
||||
|
||||
var externalAssetRefPattern = regexp.MustCompile(`\b(?:src|href)=["']https?://`)
|
||||
|
||||
func TestHomePageUsesLocalVersionedAssets(t *testing.T) {
|
||||
orig := version.Value
|
||||
version.Value = "1.2.3"
|
||||
defer func() { version.Value = orig }()
|
||||
|
||||
app := testRouter(t, testRouterSettings(t, false))
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
app.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rr.Code)
|
||||
}
|
||||
body := rr.Body.String()
|
||||
|
||||
for _, want := range []string{
|
||||
`href="/favicon.ico?v=1.2.3"`,
|
||||
`href="/favicon-16x16.png?v=1.2.3"`,
|
||||
`href="/favicon-32x32.png?v=1.2.3"`,
|
||||
`src="/assets/js/htmx@v2.0.2.min.js"`,
|
||||
`src="/assets/js/web3-charts.js?v=1.2.3"`,
|
||||
`href="/assets/css/output@1.2.3.css"`,
|
||||
`href="/assets/css/web3.css?v=1.2.3"`,
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Fatalf("expected response body to contain %q", want)
|
||||
}
|
||||
}
|
||||
|
||||
if externalAssetRefPattern.MatchString(body) {
|
||||
t.Fatalf("home page contains external asset URL: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwaggerUIUsesLocalAssetsOnly(t *testing.T) {
|
||||
app := testRouter(t, testRouterSettings(t, false))
|
||||
req := httptest.NewRequest(http.MethodGet, "/swagger/", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
app.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rr.Code)
|
||||
}
|
||||
body := rr.Body.String()
|
||||
|
||||
for _, want := range []string{
|
||||
`href="./swagger-ui.css"`,
|
||||
`src="./swagger-ui-bundle.js"`,
|
||||
`src="./swagger-ui-standalone-preset.js"`,
|
||||
`src="./swagger-initializer.js"`,
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Fatalf("expected swagger index to contain %q", want)
|
||||
}
|
||||
}
|
||||
|
||||
if externalAssetRefPattern.MatchString(body) {
|
||||
t.Fatalf("swagger index contains external asset URL: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStaticResourcesAreCacheableInReleaseMode(t *testing.T) {
|
||||
orig := version.Value
|
||||
version.Value = "1.2.3"
|
||||
defer func() { version.Value = orig }()
|
||||
|
||||
app := testRouter(t, testRouterSettings(t, false))
|
||||
|
||||
tests := []struct {
|
||||
path string
|
||||
wantCacheControl string
|
||||
}{
|
||||
{path: "/assets/css/web3.css?v=1.2.3", wantCacheControl: "public, max-age=31536000, immutable"},
|
||||
{path: "/assets/js/htmx@v2.0.2.min.js", wantCacheControl: "public, max-age=31536000, immutable"},
|
||||
{path: "/favicon.ico?v=1.2.3", wantCacheControl: "public, max-age=31536000, immutable"},
|
||||
{path: "/swagger/swagger-ui.css", wantCacheControl: "public, max-age=31536000"},
|
||||
{path: "/swagger.json", wantCacheControl: "public, max-age=31536000"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.path, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, tc.path, nil)
|
||||
rr := httptest.NewRecorder()
|
||||
app.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d for %s, got %d", http.StatusOK, tc.path, rr.Code)
|
||||
}
|
||||
|
||||
if got := rr.Header().Get("Cache-Control"); got != tc.wantCacheControl {
|
||||
t.Fatalf("unexpected Cache-Control for %s: got %q want %q", tc.path, got, tc.wantCacheControl)
|
||||
}
|
||||
if got := rr.Header().Get("Vary"); got != "Accept-Encoding" {
|
||||
t.Fatalf("unexpected Vary for %s: %q", tc.path, got)
|
||||
}
|
||||
if rr.Header().Get("Expires") == "" {
|
||||
t.Fatalf("expected Expires for %s", tc.path)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user