This repository has been archived by the owner on Oct 23, 2020. It is now read-only.
/
cache.go
144 lines (122 loc) · 3.43 KB
/
cache.go
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package gitnotify
import (
"bytes"
"fmt"
"net/http"
"strconv"
"time"
"github.com/boltdb/bolt"
)
// CacheWriterIface should be used to set the cache params
// SetCachePath should be set before Write() is ever called
// WriteFromCache will write to the writer directly . returns true when written from cache
// all status writes >= 400 are treated as uncachable
type CacheWriterIface interface {
SetCachePath(string)
WriteFromCache() bool
}
// CacheWriter is the struct to store data temporarily
type CacheWriter struct {
w http.ResponseWriter
cache bool
usedCache bool
path string
buf bytes.Buffer
}
func newCacheHandler(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cw := &CacheWriter{
w: w,
}
defer cw.Close()
h.ServeHTTP(cw, r)
})
}
// Close saves data to the underlying cache
func (cw *CacheWriter) Close() {
if cw.usedCache {
return
// do nothing
}
// save here only if header status was > 200 < 300 or blank
if cw.cache {
// log.Println("wrote to cache")
addToCache(cw.path, string(cw.buf.Bytes()))
}
}
// WriteFromCache writes to the writer if it could successfully get from cache
func (cw *CacheWriter) WriteFromCache() bool {
data, success := retrieveFromCache(cw.path)
if !success || cw.path == "" {
return success
}
cw.usedCache = true
cw.w.Write([]byte(data))
return success
}
// SetCachePath sets the cache path and cachable to true
func (cw *CacheWriter) SetCachePath(path string) {
cw.path = path
cw.cache = true
}
// Header delegates from the original
func (cw *CacheWriter) Header() http.Header {
return cw.w.Header()
}
// WriteHeader writes the headers
func (cw *CacheWriter) WriteHeader(code int) {
if code >= 300 {
if cw.cache {
cw.cache = false
}
}
cw.w.WriteHeader(code)
}
func (cw *CacheWriter) Write(b []byte) (int, error) {
if cw.cache {
cw.buf.Write(b)
}
return cw.w.Write(b)
}
// Caching will be done for 1 day. This needs to be fixed by reading from the cache headers
const cacheBucket = "gitnotify-caches"
// retrieveFromCache removes the item if its after expiry
func retrieveFromCache(cachePath string) (string, bool) {
statCount("cache.retrieve.request")
db, err := bolt.Open("cacher.db", 0600, &bolt.Options{Timeout: 1 * time.Second})
defer db.Close()
tx, err := db.Begin(true)
defer tx.Rollback()
b, err := tx.CreateBucketIfNotExists([]byte(cacheBucket))
timeStr := b.Get([]byte(cachePath + "__"))
cacheTill, err := strconv.ParseInt(string(timeStr), 10, 64)
if err != nil {
return "", false
}
if cacheTill > time.Now().Unix() {
statCount("cache.retrieve.success")
return string(b.Get([]byte(cachePath))), true
}
b.Delete([]byte(cachePath + "__"))
b.Delete([]byte(cachePath))
if err = tx.Commit(); err != nil {
return "", false
}
statCount("cache.retrieve.expired")
return "", false
}
func addToCache(cachePath string, data string) {
statCount("cache.add")
cacheTill := (time.Now().Add(time.Hour)).Unix()
db, err := bolt.Open("cacher.db", 0600, &bolt.Options{Timeout: 1 * time.Second})
defer db.Close()
tx, err := db.Begin(true)
defer tx.Rollback()
b, err := tx.CreateBucketIfNotExists([]byte(cacheBucket))
err = b.Put([]byte(cachePath), []byte(data))
err = b.Put([]byte(cachePath+"__"), []byte(fmt.Sprintf("%d", cacheTill)))
if err = tx.Commit(); err != nil {
return
}
}
// add another function which will clean up keys periodically - will be invoked via go func()