Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
54 lines
1.6 KiB
Go
54 lines
1.6 KiB
Go
//go:build embed || unit
|
|
|
|
package web
|
|
|
|
import (
|
|
"net/http"
|
|
"path"
|
|
"strings"
|
|
)
|
|
|
|
// Vite emits content-hashed filenames under assets/, so the backend can apply
|
|
// immutable caching without relying on a reverse proxy to classify paths.
|
|
const staticAssetsCacheControl = "public, max-age=31536000, immutable"
|
|
|
|
// isFingerprintedEmbeddedAssetPath reports whether a cleaned URL path refers to
|
|
// a Vite asset whose filename contains the default eight-character build hash.
|
|
func isFingerprintedEmbeddedAssetPath(cleanPath string) bool {
|
|
cleanPath = strings.TrimPrefix(cleanPath, "/")
|
|
if !strings.HasPrefix(cleanPath, "assets/") {
|
|
return false
|
|
}
|
|
|
|
filename := path.Base(cleanPath)
|
|
extension := path.Ext(filename)
|
|
stem := strings.TrimSuffix(filename, extension)
|
|
const fingerprintLength = 8
|
|
delimiterIndex := len(stem) - fingerprintLength - 1
|
|
if extension == "" || delimiterIndex < 1 || stem[delimiterIndex] != '-' {
|
|
return false
|
|
}
|
|
|
|
// Vite hashes use URL-safe characters and are stable for immutable caching.
|
|
fingerprint := stem[delimiterIndex+1:]
|
|
for _, char := range fingerprint {
|
|
if (char >= 'a' && char <= 'z') ||
|
|
(char >= 'A' && char <= 'Z') ||
|
|
(char >= '0' && char <= '9') ||
|
|
char == '_' || char == '-' {
|
|
continue
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// applyStaticAssetCacheHeaders sets Cache-Control for long-cacheable static paths.
|
|
// index.html / SPA routes must keep no-cache and are not handled here.
|
|
func applyStaticAssetCacheHeaders(header http.Header, cleanPath string) {
|
|
if header == nil || !isFingerprintedEmbeddedAssetPath(cleanPath) {
|
|
return
|
|
}
|
|
header.Set("Cache-Control", staticAssetsCacheControl)
|
|
}
|