package main import ( "bufio" "bytes" "encoding/json" "io" "io/ioutil" "strconv" "strings" "time" "github.com/ginuerzh/gost" ) type peerConfig struct { Strategy string `json:"strategy"` MaxFails int `json:"max_fails"` FailTimeout time.Duration `json:"fail_timeout"` period time.Duration // the period for live reloading Nodes []string `json:"nodes"` group *gost.NodeGroup baseNodes []gost.Node } type bypass struct { Reverse bool `json:"reverse"` Patterns []string `json:"patterns"` } func parsePeerConfig(cfg string, group *gost.NodeGroup, baseNodes []gost.Node) *peerConfig { pc := &peerConfig{ group: group, baseNodes: baseNodes, } go gost.PeriodReload(pc, cfg) return pc } func (cfg *peerConfig) Validate() { if cfg.MaxFails <= 0 { cfg.MaxFails = 1 } if cfg.FailTimeout <= 0 { cfg.FailTimeout = 30 // seconds } } func (cfg *peerConfig) Reload(r io.Reader) error { if err := cfg.parse(r); err != nil { return err } cfg.Validate() group := cfg.group strategy := cfg.Strategy if len(cfg.baseNodes) > 0 { // overwrite the strategry in the peer config if `strategy` param exists. if s := cfg.baseNodes[0].Get("strategy"); s != "" { strategy = s } } group.Options = append([]gost.SelectOption{}, gost.WithFilter(&gost.FailFilter{ MaxFails: cfg.MaxFails, FailTimeout: time.Duration(cfg.FailTimeout) * time.Second, }), gost.WithStrategy(parseStrategy(strategy)), ) gNodes := cfg.baseNodes nid := len(gNodes) + 1 for _, s := range cfg.Nodes { nodes, err := parseChainNode(s) if err != nil { return err } for i := range nodes { nodes[i].ID = nid nid++ } gNodes = append(gNodes, nodes...) } group.SetNodes(gNodes...) return nil } func (cfg *peerConfig) parse(r io.Reader) error { data, err := ioutil.ReadAll(r) if err != nil { return err } // compatible with JSON format if err := json.NewDecoder(bytes.NewReader(data)).Decode(cfg); err == nil { return nil } split := func(line string) []string { if line == "" { return nil } if n := strings.IndexByte(line, '#'); n >= 0 { line = line[:n] } line = strings.Replace(line, "\t", " ", -1) line = strings.TrimSpace(line) var ss []string for _, s := range strings.Split(line, " ") { if s = strings.TrimSpace(s); s != "" { ss = append(ss, s) } } return ss } cfg.Nodes = nil scanner := bufio.NewScanner(bytes.NewReader(data)) for scanner.Scan() { line := scanner.Text() ss := split(line) if len(ss) < 2 { continue } switch ss[0] { case "strategy": cfg.Strategy = ss[1] case "max_fails": cfg.MaxFails, _ = strconv.Atoi(ss[1]) case "fail_timeout": cfg.FailTimeout, _ = time.ParseDuration(ss[1]) case "reload": cfg.period, _ = time.ParseDuration(ss[1]) case "peer": cfg.Nodes = append(cfg.Nodes, ss[1]) } } return scanner.Err() } func (cfg *peerConfig) Period() time.Duration { return cfg.period } func parseStrategy(s string) gost.Strategy { switch s { case "random": return &gost.RandomStrategy{} case "fifo": return &gost.FIFOStrategy{} case "round": fallthrough default: return &gost.RoundStrategy{} } }