Repository navigation
Expand file tree
/
Copy pathhelpers.go
More file actions
84 lines (73 loc) · 2.43 KB
/
Copy pathhelpers.go
File metadata and controls
84 lines (73 loc) · 2.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package server
import (
"net/http"
"strconv"
"strings"
"time"
)
func isCacheable(r *http.Request) bool {
// Only responses of GET requests are cached
if r.Method != http.MethodGet {
return false
}
// Authorization header -> do not cache
if r.Header.Get("Authorization") != "" {
return false
}
return true
}
func isResponseCacheable(resp *http.Response) (isCacheable bool, cacheTTl time.Duration) {
// must: have 'status: 200 OK' , not have 'Set-Cookie: ...' header , not have 'Vary: *' header
// 2. if Set-Cookie present -> user-specific -> do not cache
// 3. if 'Vary: *' -> Implies that the response is uncacheable. (https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Vary)
if resp.StatusCode != http.StatusOK || resp.Header.Get("Set-Cookie") != "" || resp.Header.Get("Vary") == "*" {
return false, 0
}
// analyze Cache-Control header (if present)
if shouldCache, cacheTTl := analyzeCacheControlHeader(resp); !shouldCache {
return false, cacheTTl // if shouldCache'd be false -> cacheTTl'll be 0
} else {
return true, cacheTTl
}
}
// Analyzes Cache-Control header and determines the response should cache or not,
// and if should cache: tries to extract caching-ttl (from 's-maxage' or 'max-age').
func analyzeCacheControlHeader(resp *http.Response) (shouldCache bool, cacheTTl time.Duration) {
ccHeader := resp.Header.Get("Cache-Control")
if ccHeader == "" {
return true, 0 // cacheTTl=0 --> means use DefaultCacheTTL for expiration
}
// parse Cache-Control header
parsedCCHeader := make(map[string]string)
parts := strings.SplitSeq(ccHeader, ",")
for part := range parts {
part = strings.TrimSpace(part)
if strings.Contains(part, "=") {
kv := strings.SplitN(part, "=", 2)
parsedCCHeader[strings.ToLower(kv[0])] = strings.Trim(kv[1], `"`)
} else {
parsedCCHeader[strings.ToLower(part)] = ""
}
}
// no-store -> must not cache
if _, ok := parsedCCHeader["no-store"]; ok {
return false, 0
}
// no-cache -> requires revalidation (skip for now)
if _, ok := parsedCCHeader["no-cache"]; ok {
return false, 0
}
// s-maxage (proxy-specific)
if v, ok := parsedCCHeader["s-maxage"]; ok {
if secs, err := strconv.Atoi(v); err == nil && secs > 0 {
return true, time.Duration(secs) * time.Second
}
}
// max-age
if v, ok := parsedCCHeader["max-age"]; ok {
if secs, err := strconv.Atoi(v); err == nil && secs > 0 {
return true, time.Duration(secs) * time.Second
}
}
return true, 0
}