forked from flashmob/go-guerrilla
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.go
233 lines (216 loc) · 6.61 KB
/
config.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
package guerrilla
import (
"encoding/json"
"errors"
"fmt"
"os"
"reflect"
"strings"
)
// AppConfig is the holder of the configuration of the app
type AppConfig struct {
Servers []ServerConfig `json:"servers"`
AllowedHosts []string `json:"allowed_hosts"`
PidFile string `json:"pid_file"`
}
// ServerConfig specifies config options for a single server
type ServerConfig struct {
IsEnabled bool `json:"is_enabled"`
Hostname string `json:"host_name"`
MaxSize int64 `json:"max_size"`
PrivateKeyFile string `json:"private_key_file"`
PublicKeyFile string `json:"public_key_file"`
Timeout int `json:"timeout"`
ListenInterface string `json:"listen_interface"`
StartTLSOn bool `json:"start_tls_on,omitempty"`
TLSAlwaysOn bool `json:"tls_always_on,omitempty"`
MaxClients int `json:"max_clients"`
LogFile string `json:"log_file,omitempty"`
_privateKeyFile_mtime int
_publicKeyFile_mtime int
}
// Unmarshalls json data into AppConfig struct and any other initialization of the struct
func (c *AppConfig) Load(jsonBytes []byte) error {
err := json.Unmarshal(jsonBytes, c)
if err != nil {
return fmt.Errorf("could not parse config file: %s", err)
}
if len(c.AllowedHosts) == 0 {
return errors.New("empty AllowedHosts is not allowed")
}
// read the timestamps for the ssl keys, to determine if they need to be reloaded
for i := 0; i < len(c.Servers); i++ {
if err := c.Servers[i].loadTlsKeyTimestamps(); err != nil {
return err
}
}
return nil
}
// Emits any configuration change events onto the event bus.
func (c *AppConfig) EmitChangeEvents(oldConfig *AppConfig, app Guerrilla) {
// has 'allowed hosts' changed?
if !reflect.DeepEqual(oldConfig.AllowedHosts, c.AllowedHosts) {
app.Publish("config_change:allowed_hosts", c)
}
// has pid file changed?
if strings.Compare(oldConfig.PidFile, c.PidFile) != 0 {
app.Publish("config_change:pid_file", c)
}
// server config changes
oldServers := oldConfig.getServers()
for iface, newServer := range c.getServers() {
// is server is in both configs?
if oldServer, ok := oldServers[iface]; ok {
// since old server exists in the new config, we do not track it anymore
delete(oldServers, iface)
newServer.emitChangeEvents(oldServer, app)
} else {
// start new server
app.Publish("server_change:new_server", newServer)
}
}
// remove any servers that don't exist anymore
for _, oldserver := range oldServers {
app.Publish("server_change:remove_server", oldserver)
}
}
// gets the servers in a map (key by interface) for easy lookup
func (c *AppConfig) getServers() map[string]*ServerConfig {
servers := make(map[string]*ServerConfig, len(c.Servers))
for i := 0; i < len(c.Servers); i++ {
servers[c.Servers[i].ListenInterface] = &c.Servers[i]
}
return servers
}
// Emits any configuration change events on the server.
// All events are fired and run synchronously
func (sc *ServerConfig) emitChangeEvents(oldServer *ServerConfig, app Guerrilla) {
// get a list of changes
changes := getDiff(
*oldServer,
*sc,
)
if len(changes) > 0 {
// something changed in the server config
app.Publish("server_change:update_config", sc)
}
// enable or disable?
if _, ok := changes["IsEnabled"]; ok {
if sc.IsEnabled {
app.Publish("server_change:start_server", sc)
} else {
app.Publish("server_change:stop_server", sc)
}
// do not emit any more events when IsEnabled changed
return
}
// log file change?
if _, ok := changes["LogFile"]; ok {
app.Publish("server_change:"+sc.ListenInterface+":new_log_file", sc)
} else {
// since config file has not changed, we reload it
app.Publish("server_change:"+sc.ListenInterface+":reopen_log_file", sc)
}
// timeout changed
if _, ok := changes["Timeout"]; ok {
app.Publish("server_change:timeout", sc)
}
// max_clients changed
if _, ok := changes["MaxClients"]; ok {
app.Publish("server_change:max_clients", sc)
}
// tls changed
if ok := func() bool {
if _, ok := changes["PrivateKeyFile"]; ok {
return true
}
if _, ok := changes["PublicKeyFile"]; ok {
return true
}
if _, ok := changes["StartTLSOn"]; ok {
return true
}
if _, ok := changes["TLSAlwaysOn"]; ok {
return true
}
return false
}(); ok {
app.Publish("server_change:tls_config", sc)
}
}
// Loads in timestamps for the ssl keys
func (sc *ServerConfig) loadTlsKeyTimestamps() error {
var statErr = func(iface string, err error) error {
return errors.New(
fmt.Sprintf(
"could not stat key for server [%s], %s",
iface,
err.Error()))
}
if info, err := os.Stat(sc.PrivateKeyFile); err == nil {
sc._privateKeyFile_mtime = info.ModTime().Second()
} else {
return statErr(sc.ListenInterface, err)
}
if info, err := os.Stat(sc.PublicKeyFile); err == nil {
sc._publicKeyFile_mtime = info.ModTime().Second()
} else {
return statErr(sc.ListenInterface, err)
}
return nil
}
// Gets the timestamp of the TLS certificates. Returns a unix time of when they were last modified
// when the config was read. We use this info to determine if TLS needs to be re-loaded.
func (sc *ServerConfig) getTlsKeyTimestamps() (int, int) {
return sc._privateKeyFile_mtime, sc._publicKeyFile_mtime
}
// Returns a diff between struct a & struct b.
// Results are returned in a map, where each key is the name of the field that was different.
// a and b are struct values, must not be pointer
// and of the same struct type
func getDiff(a interface{}, b interface{}) map[string]interface{} {
ret := make(map[string]interface{}, 5)
compareWith := structtomap(b)
for key, val := range structtomap(a) {
if val != compareWith[key] {
ret[key] = compareWith[key]
}
}
// detect tls changes (have the key files been modified?)
if oldServer, ok := a.(ServerConfig); ok {
t1, t2 := oldServer.getTlsKeyTimestamps()
if newServer, ok := b.(ServerConfig); ok {
t3, t4 := newServer.getTlsKeyTimestamps()
if t1 != t3 {
ret["PrivateKeyFile"] = newServer.PrivateKeyFile
}
if t2 != t4 {
ret["PublicKeyFile"] = newServer.PublicKeyFile
}
}
}
return ret
}
// Convert fields of a struct to a map
// only able to convert int, bool and string; not recursive
func structtomap(obj interface{}) map[string]interface{} {
ret := make(map[string]interface{}, 0)
v := reflect.ValueOf(obj)
t := v.Type()
for index := 0; index < v.NumField(); index++ {
vField := v.Field(index)
fName := t.Field(index).Name
switch vField.Kind() {
case reflect.Int:
value := vField.Int()
ret[fName] = value
case reflect.String:
value := vField.String()
ret[fName] = value
case reflect.Bool:
value := vField.Bool()
ret[fName] = value
}
}
return ret
}