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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
// Package mapcache is very similar to rediscache, except there are no
// external dependencies and the page output is saved in a map
package mapcache
import (
"bytes"
"net/http"
"sync"
)
type responseWriter struct {
Headers http.Header
StatusCode int
Data []byte
buf *bytes.Buffer
}
func (rw *responseWriter) Header() http.Header {
if rw.Headers == nil {
rw.Headers = http.Header{}
}
return rw.Headers
}
func (rw *responseWriter) Write(msg []byte) (int, error) {
if rw.buf == nil {
rw.buf = &bytes.Buffer{}
}
return rw.buf.Write(msg) //nolint:wrapcheck
}
// Simply for satisfying the http.ResponseWriter interface.
func (rw *responseWriter) WriteHeader(code int) {
rw.StatusCode = code
}
// WriteData takes the internal buffer and writes out the entire
// contents to the 'Data' field for storage in Redis.
func (rw *responseWriter) WriteData() {
rw.Data = rw.buf.Bytes()
}
func New() *Cache {
return &Cache{
m: &sync.RWMutex{},
cached: map[string]*responseWriter{},
}
}
type Cache struct {
m *sync.RWMutex
cached map[string]*responseWriter
}
func (c *Cache) Remove(path string) {
c.m.Lock()
delete(c.cached, path)
c.m.Unlock()
}
func (c *Cache) Clear() {
c.m.Lock()
c.cached = map[string]*responseWriter{}
c.m.Unlock()
}
// HandleWithParams is the same as Handle but caches for the GET params
// rather than discarding them.
func (c *Cache) HandleWithParams(next http.Handler) http.Handler {
return c.handle(true, next)
}
// Handle is a Simple function that will cache the response for given handler in
// redis and instead of responding with the result from the handler it will
// simply dump the contents of the redis key if it exists.
func (c *Cache) Handle(next http.Handler) http.Handler {
return c.handle(false, next)
}
func (c *Cache) handle(params bool, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
subkey := r.URL.Path
if params {
subkey = r.URL.Path + "?" + r.URL.RawQuery
}
content:
c.m.RLock()
rw, ok := c.cached[subkey]
c.m.RUnlock()
if !ok {
rw := &responseWriter{}
next.ServeHTTP(rw, r)
rw.WriteData()
c.m.Lock()
c.cached[subkey] = rw
c.m.Unlock()
// We got the content, let's go back around again and dump
// it out from the cache
goto content
}
if rw.Headers != nil {
for k, v := range rw.Headers {
w.Header()[k] = v
}
}
if rw.StatusCode != 0 {
w.WriteHeader(rw.StatusCode)
}
_, _ = w.Write(rw.Data)
})
}
|