135 lines
2.8 KiB
Go
135 lines
2.8 KiB
Go
package proxy
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// LoadConfig reads a JSON config file and validates the result.
|
|
//
|
|
// cfg, errorValue := proxy.LoadConfig("config.json")
|
|
// if errorValue != nil {
|
|
// return
|
|
// }
|
|
func LoadConfig(path string) (*Config, error) {
|
|
data, errorValue := os.ReadFile(path)
|
|
if errorValue != nil {
|
|
return nil, errorValue
|
|
}
|
|
|
|
config := &Config{}
|
|
if errorValue = json.Unmarshal(data, config); errorValue != nil {
|
|
return nil, errorValue
|
|
}
|
|
config.sourcePath = path
|
|
|
|
if errorValue = config.Validate(); errorValue != nil {
|
|
return nil, errorValue
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
// Validate checks that `bind` and `pools` are present and every enabled pool has a URL.
|
|
//
|
|
// cfg := &proxy.Config{
|
|
// Bind: []proxy.BindAddr{{Host: "127.0.0.1", Port: 3333}},
|
|
// Pools: []proxy.PoolConfig{{URL: "pool-a:3333", Enabled: true}},
|
|
// }
|
|
// if errorValue := cfg.Validate(); errorValue != nil {
|
|
// return
|
|
// }
|
|
func (c *Config) Validate() error {
|
|
if c == nil {
|
|
return errors.New("config is nil")
|
|
}
|
|
if len(c.Bind) == 0 {
|
|
return errors.New("bind list is empty")
|
|
}
|
|
if len(c.Pools) == 0 {
|
|
return errors.New("pool list is empty")
|
|
}
|
|
|
|
for _, poolConfig := range c.Pools {
|
|
if poolConfig.Enabled && strings.TrimSpace(poolConfig.URL) == "" {
|
|
return errors.New("enabled pool URL is empty")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewConfigWatcher watches a config file and reloads the proxy on modification.
|
|
//
|
|
// w := proxy.NewConfigWatcher("config.json", func(cfg *proxy.Config) {
|
|
// p.Reload(cfg)
|
|
// })
|
|
func NewConfigWatcher(path string, onChange func(*Config)) *ConfigWatcher {
|
|
return newConfigWatcher(path, onChange, true)
|
|
}
|
|
|
|
func newConfigWatcher(path string, onChange func(*Config), enabled bool) *ConfigWatcher {
|
|
return &ConfigWatcher{
|
|
path: path,
|
|
onChange: onChange,
|
|
enabled: enabled,
|
|
done: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// Start begins 1-second polling for `config.json`.
|
|
//
|
|
// w.Start()
|
|
func (w *ConfigWatcher) Start() {
|
|
if w == nil || !w.enabled {
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
ticker := time.NewTicker(time.Second)
|
|
defer ticker.Stop()
|
|
if info, errorValue := os.Stat(w.path); errorValue == nil {
|
|
w.lastModifiedAt = info.ModTime()
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
info, errorValue := os.Stat(w.path)
|
|
if errorValue != nil {
|
|
continue
|
|
}
|
|
|
|
if !info.ModTime().After(w.lastModifiedAt) {
|
|
continue
|
|
}
|
|
w.lastModifiedAt = info.ModTime()
|
|
|
|
config, errorValue := LoadConfig(w.path)
|
|
if errorValue == nil && w.onChange != nil {
|
|
w.onChange(config)
|
|
}
|
|
case <-w.done:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Stop ends polling so the watcher can be shut down with `p.Stop()`.
|
|
//
|
|
// w.Stop()
|
|
func (w *ConfigWatcher) Stop() {
|
|
if w == nil || w.done == nil {
|
|
return
|
|
}
|
|
|
|
select {
|
|
case <-w.done:
|
|
default:
|
|
close(w.done)
|
|
}
|
|
}
|