-
Notifications
You must be signed in to change notification settings - Fork 0
/
handler.go
131 lines (115 loc) · 3.18 KB
/
handler.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
package bazelcache
import (
"bytes"
"context"
"errors"
"io"
"log"
"net/http"
"strings"
)
var ErrNotFound = errors.New("resource was not found")
type BazelCache interface {
// Get retrieves the object with a given key
Get(ctx context.Context, key string) ([]byte, error)
// Put stores the object with a given key
Put(ctx context.Context, key string, r io.Reader) error
// Delete deletes the object with a given key
Delete(ctx context.Context, key string) error
// Has checks wether an object with a given key exists.
Has(ctx context.Context, key string) bool
}
type chainCache struct {
bcs []BazelCache
}
// NewChainCache provides a chained cache. On write it tries to write to all
// underlying caches and on read it returns the first cache hit.
func NewChainCache(bcs ...BazelCache) BazelCache {
return chainCache{bcs}
}
// Get retrieves the object with a given key from the first available cache.
func (cc chainCache) Get(ctx context.Context, key string) (out []byte, err error) {
for _, bc := range cc.bcs {
if out, err = bc.Get(ctx, key); err == nil {
return out, nil
}
}
return nil, err
}
// Put stores the object with a given key, returns success if at least one of
// the underlying cache suceesfully store the value.
func (cc chainCache) Put(ctx context.Context, key string, r io.Reader) error {
data, err := io.ReadAll(r)
if err != nil {
return err
}
for _, bc := range cc.bcs {
if err1 := bc.Put(ctx, key, bytes.NewReader(data)); err1 == nil {
err = nil
}
}
return err
}
// Delete deletes the object with a given key from all the cache
func (cc chainCache) Delete(ctx context.Context, key string) error {
for _, bc := range cc.bcs {
bc.Delete(ctx, key)
}
return nil
}
// Has checks wether an object with a given key exists in any of the caches.
func (cc chainCache) Has(ctx context.Context, key string) bool {
for _, bc := range cc.bcs {
if bc.Has(ctx, key) {
return true
}
}
return false
}
type cacheServer struct {
bc BazelCache
logger *log.Logger
}
func (cs cacheServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
key := strings.Trim(r.URL.Path, "/")
w.Header().Set("Allow", "GET, PUT, HEAD, DELETE")
status := http.StatusOK
defer func() { cs.logger.Printf("[%s] %s [%v]", r.Method, key, status) }()
switch r.Method {
case http.MethodGet:
out, err := cs.bc.Get(r.Context(), key)
if err != nil {
if err == ErrNotFound {
status = http.StatusNotFound
} else {
status = http.StatusInternalServerError
}
} else {
w.Write(out)
return
}
case http.MethodPut:
if err := cs.bc.Put(r.Context(), key, r.Body); err != nil {
cs.logger.Print(err)
status = http.StatusInternalServerError
}
case http.MethodDelete:
if err := cs.bc.Delete(r.Context(), key); err != nil {
status = http.StatusInternalServerError
}
case http.MethodHead:
if cs.bc.Has(r.Context(), key) {
status = http.StatusFound
} else {
status = http.StatusNotFound
}
default:
status = http.StatusMethodNotAllowed
}
w.WriteHeader(status)
}
// NewCacheServer converts a BazelCache object to a handler compatible with
// Bazel remote cahce.
func NewCacheServer(bc BazelCache) http.Handler {
return cacheServer{bc, log.Default()}
}