Repository navigation
Expand file tree
/
Copy pathcache.go
More file actions
75 lines (64 loc) · 2.57 KB
/
Copy pathcache.go
File metadata and controls
75 lines (64 loc) · 2.57 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
package ehttp
import (
"strconv"
"strings"
"time"
"go.uber.org/zap"
)
// SetCacheHeader sets Cache-Control with Set. It is for a handler. A
// middleware that must own the field uses CacheControl with Own.
func SetCacheHeader(r *ResponseWriter, d time.Duration) {
SetCacheHeaderPrivate(r, d, false)
}
// SetCacheHeaderPrivate is SetCacheHeader with a private flag. If private is
// true and d is not 0, the response gets the `private` directive: a shared
// cache must not store it (RFC 9111 §3.5).
//
// A negative d, and a d above 0 and under 1 second, are mistakes of the
// developer: max-age takes whole seconds (RFC 9111 §1.2.2). In Development it
// panics. In Production it logs an error and sends no-store. A d of 1.5s is
// cut to 1 second.
func SetCacheHeaderPrivate(r *ResponseWriter, d time.Duration, private bool) {
if d < 0 {
r.fail(strictError{
level: zap.ErrorLevel,
rule: "negative cache duration",
what: "ehttp.SetCacheHeader(" + d.String() + ")",
why: "max-age is delta-seconds, a non-negative integer (RFC 9111 §1.2.2); the response gets no-store",
fix: "use zero for no-store, or a positive duration, for example ehttp.SetCacheHeader(res, time.Minute)",
})
}
if d > 0 && d < time.Second {
r.fail(strictError{
level: zap.ErrorLevel,
rule: "sub-second cache duration",
what: "ehttp.SetCacheHeader(" + d.String() + ")",
why: "max-age takes whole seconds (delta-seconds, RFC 9111 §1.2.2), so it would send max-age=0; the response gets no-store",
fix: "use 1s or more, or 0 for no-store, for example ehttp.SetCacheHeader(res, time.Minute)",
})
d = 0
}
r.Set(HeaderCacheControl, CacheControl(d, private))
}
// CacheControl returns the Cache-Control value of SetCacheHeaderPrivate. It
// has no side effect. A middleware passes it to Own. A duration of zero or
// less gives no-store: a negative max-age is not valid (RFC 9111 §1.2.2). It
// does not check the duration: a duration under 1 second gives max-age=0. Use
// SetCacheHeader in a handler to get the strict check.
func CacheControl(d time.Duration, private bool) string {
if d <= 0 {
return "no-store, no-cache, must-revalidate, proxy-revalidate"
}
max_age := strconv.Itoa(int(d.Seconds()))
stale_while_revalidate := strconv.Itoa(int(d.Seconds()))
stale_if_error := strconv.Itoa(int(d.Seconds()))
cache_control := []string{
"max-age=" + max_age,
"stale-while-revalidate=" + stale_while_revalidate,
"stale-if-error=" + stale_if_error,
}
if private {
cache_control = append([]string{"private"}, cache_control...)
}
return strings.Join(cache_control, ", ")
}