2020-06-17 06:06:40 +00:00
|
|
|
package middleware
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2022-04-26 23:01:45 +00:00
|
|
|
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
2020-06-17 06:06:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Cache struct {
|
|
|
|
Cacheability Cacheability
|
|
|
|
NoCache bool
|
|
|
|
NoStore bool
|
|
|
|
MaxAge time.Duration
|
|
|
|
SharedMaxAge time.Duration
|
|
|
|
NoTransform bool
|
|
|
|
Revalidation Revalidation
|
|
|
|
}
|
|
|
|
|
|
|
|
type Cacheability string
|
|
|
|
|
|
|
|
const (
|
|
|
|
CacheabilityNotSet Cacheability = ""
|
2022-08-16 05:04:36 +00:00
|
|
|
CacheabilityPublic Cacheability = "public"
|
|
|
|
CacheabilityPrivate Cacheability = "private"
|
2020-06-17 06:06:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Revalidation string
|
|
|
|
|
|
|
|
const (
|
|
|
|
RevalidationNotSet Revalidation = ""
|
2022-08-16 05:04:36 +00:00
|
|
|
RevalidationMust Revalidation = "must-revalidate"
|
|
|
|
RevalidationProxy Revalidation = "proxy-revalidate"
|
2020-06-17 06:06:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type CacheConfig struct {
|
2022-02-14 16:22:30 +00:00
|
|
|
MaxAge time.Duration
|
|
|
|
SharedMaxAge time.Duration
|
2020-06-17 06:06:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
NeverCacheOptions = &Cache{
|
|
|
|
NoStore: true,
|
|
|
|
}
|
|
|
|
AssetOptions = func(maxAge, SharedMaxAge time.Duration) *Cache {
|
|
|
|
return &Cache{
|
|
|
|
Cacheability: CacheabilityPublic,
|
|
|
|
MaxAge: maxAge,
|
|
|
|
SharedMaxAge: SharedMaxAge,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2022-08-16 05:04:36 +00:00
|
|
|
func NoCacheInterceptor() *cacheInterceptor {
|
|
|
|
return CacheInterceptorOpts(NeverCacheOptions)
|
2020-06-17 06:06:40 +00:00
|
|
|
}
|
|
|
|
|
2022-08-16 05:04:36 +00:00
|
|
|
func AssetsCacheInterceptor(maxAge, sharedMaxAge time.Duration) *cacheInterceptor {
|
|
|
|
return CacheInterceptorOpts(AssetOptions(maxAge, sharedMaxAge))
|
2020-06-17 06:06:40 +00:00
|
|
|
}
|
|
|
|
|
2022-08-16 05:04:36 +00:00
|
|
|
func CacheInterceptorOpts(cache *Cache) *cacheInterceptor {
|
|
|
|
return &cacheInterceptor{
|
|
|
|
cache: cache,
|
|
|
|
}
|
2020-06-17 06:06:40 +00:00
|
|
|
}
|
|
|
|
|
2022-08-16 05:04:36 +00:00
|
|
|
type cacheInterceptor struct {
|
|
|
|
cache *Cache
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cacheInterceptor) Handler(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
next.ServeHTTP(&cachingResponseWriter{
|
2021-07-28 11:19:44 +00:00
|
|
|
ResponseWriter: w,
|
2022-08-16 05:04:36 +00:00
|
|
|
Cache: c.cache,
|
|
|
|
}, r)
|
2020-06-17 06:06:40 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-08-16 05:04:36 +00:00
|
|
|
func (c *cacheInterceptor) HandlerFunc(next http.HandlerFunc) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
next.ServeHTTP(&cachingResponseWriter{
|
|
|
|
ResponseWriter: w,
|
|
|
|
Cache: c.cache,
|
|
|
|
}, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-28 11:19:44 +00:00
|
|
|
type cachingResponseWriter struct {
|
|
|
|
http.ResponseWriter
|
|
|
|
*Cache
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *cachingResponseWriter) WriteHeader(code int) {
|
|
|
|
if code >= 400 {
|
|
|
|
NeverCacheOptions.serializeHeaders(w.ResponseWriter)
|
|
|
|
w.ResponseWriter.WriteHeader(code)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w.Cache.serializeHeaders(w.ResponseWriter)
|
|
|
|
w.ResponseWriter.WriteHeader(code)
|
|
|
|
}
|
|
|
|
|
2020-06-17 06:06:40 +00:00
|
|
|
func (c *Cache) serializeHeaders(w http.ResponseWriter) {
|
|
|
|
control := make([]string, 0, 6)
|
|
|
|
pragma := false
|
|
|
|
|
2024-08-23 12:43:46 +00:00
|
|
|
// Do not overwrite cache-control header if set by business logic.
|
|
|
|
if w.Header().Get(http_utils.CacheControl) != "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-06-17 06:06:40 +00:00
|
|
|
if c.Cacheability != CacheabilityNotSet {
|
|
|
|
control = append(control, string(c.Cacheability))
|
|
|
|
control = append(control, fmt.Sprintf("max-age=%v", c.MaxAge.Seconds()))
|
|
|
|
if c.SharedMaxAge != c.MaxAge {
|
|
|
|
control = append(control, fmt.Sprintf("s-maxage=%v", c.SharedMaxAge.Seconds()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
maxAge := c.MaxAge
|
|
|
|
if maxAge == 0 {
|
|
|
|
maxAge = -time.Hour
|
|
|
|
}
|
|
|
|
expires := time.Now().UTC().Add(maxAge).Format(http.TimeFormat)
|
|
|
|
|
|
|
|
if c.NoCache {
|
2022-08-16 05:04:36 +00:00
|
|
|
control = append(control, "no-cache")
|
2020-06-17 06:06:40 +00:00
|
|
|
pragma = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.NoStore {
|
2022-08-16 05:04:36 +00:00
|
|
|
control = append(control, "no-store")
|
2020-06-17 06:06:40 +00:00
|
|
|
pragma = true
|
|
|
|
}
|
|
|
|
if c.NoTransform {
|
2022-08-16 05:04:36 +00:00
|
|
|
control = append(control, "no-transform")
|
2020-06-17 06:06:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if c.Revalidation != RevalidationNotSet {
|
|
|
|
control = append(control, string(c.Revalidation))
|
|
|
|
}
|
|
|
|
|
2020-07-08 11:56:37 +00:00
|
|
|
w.Header().Set(http_utils.CacheControl, strings.Join(control, ", "))
|
|
|
|
w.Header().Set(http_utils.Expires, expires)
|
2020-06-17 06:06:40 +00:00
|
|
|
if pragma {
|
2020-07-08 11:56:37 +00:00
|
|
|
w.Header().Set(http_utils.Pragma, "no-cache")
|
2020-06-17 06:06:40 +00:00
|
|
|
}
|
|
|
|
}
|