/
hotconf.go
146 lines (124 loc) · 3.19 KB
/
hotconf.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
145
146
package hotconf
import (
"encoding/json"
"expvar"
"io/ioutil"
"net/http"
"strings"
)
const PathPrefix = "/hotconf/"
// HotConfHandler handle the change event of a key, and return the current value
type HotConfHandler func(key, val string) (interface{}, error)
// HotConf means the config option can be hot changed without restarting the process
type HotConf interface {
// Handle should be guaranteed thread-safe by the user
Handle(key string, currentValue interface{}, h HotConfHandler)
}
// ConfValue is the value set by hotconf
type ConfValue struct {
Pre interface{}
Current interface{}
Default interface{}
}
func (cv *ConfValue) String() string {
data, _ := json.Marshal(cv)
return string(data)
}
// An implementation of HotConf interface by HTTP
type httpHotConf struct {
s *http.ServeMux
kv map[string]*ConfValue
handlers map[string]HotConfHandler
}
// URL: /hotconf/:key?querystring
// Get Value:
// Get /hotconf/:key
// Set Value:
// POST /hotconf/:key
// with json encoded body
func (c *httpHotConf) liveChange(w http.ResponseWriter, req *http.Request) {
path := req.RequestURI
if !strings.HasPrefix(path, PathPrefix) {
// It should be not possible to be here
w.WriteHeader(http.StatusInternalServerError)
return
}
key := path[len(PathPrefix):]
if req.Method == http.MethodGet {
if strings.TrimSpace(key) == "" {
c.list(w)
return
}
c.get(key, w)
} else if req.Method == http.MethodPost {
val, err := ioutil.ReadAll(req.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
c.set(key, string(val), w)
} else {
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func (c *httpHotConf) list(w http.ResponseWriter) {
data, err := json.Marshal(c.kv)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(data)
}
func (c *httpHotConf) get(key string, w http.ResponseWriter) {
if val := c.kv[key]; val != nil {
w.WriteHeader(http.StatusOK)
w.Write([]byte(val.String()))
return
}
w.WriteHeader(http.StatusNotFound)
}
func (c *httpHotConf) set(key, val string, w http.ResponseWriter) {
if f := c.handlers[key]; f != nil {
current, err := f(key, val)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
cv := c.kv[key]
if cv == nil {
// should not be here
return
}
cv.Pre = cv.Current
cv.Current = current
return
}
w.WriteHeader(http.StatusNotFound)
}
func (c *httpHotConf) String() string {
data, _ := json.Marshal(c.kv)
return string(data)
}
func (c *httpHotConf) Handle(key string, currentValue interface{}, h HotConfHandler) {
c.kv[key] = &ConfValue{Current: currentValue, Default: currentValue}
c.handlers[key] = h
}
func New() HotConf {
// Use the default multiplexer
hc := &httpHotConf{s: http.DefaultServeMux,
kv: make(map[string]*ConfValue),
handlers: make(map[string]HotConfHandler)}
hc.s.HandleFunc("/hotconf/", hc.liveChange)
expvar.Publish("hotconf", hc)
return hc
}
var hc HotConf
//register to the default http server
func init() {
hc = New()
}
func Handle(key string, currentValue interface{}, h HotConfHandler) {
hc.Handle(key, currentValue, h)
}