mirror of
https://github.com/nadoo/glider.git
synced 2025-02-23 17:35:40 +08:00
proxy: support https health checking
This commit is contained in:
parent
826695df9a
commit
71c7cd2823
@ -100,10 +100,11 @@ glider -h
|
|||||||
<summary>click to see details</summary>
|
<summary>click to see details</summary>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
glider 0.15.1 usage:
|
glider 0.15.2 usage:
|
||||||
-check string
|
-check string
|
||||||
check=tcp[://HOST:PORT]: tcp port connect check
|
check=tcp[://HOST:PORT]: tcp port connect check
|
||||||
check=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]
|
check=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]
|
||||||
|
check=https://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]
|
||||||
check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR
|
check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR
|
||||||
check=disable: disable health check (default "http://www.msftconnecttest.com/connecttest.txt#expect=200")
|
check=disable: disable health check (default "http://www.msftconnecttest.com/connecttest.txt#expect=200")
|
||||||
-checkdisabledonly
|
-checkdisabledonly
|
||||||
|
@ -51,7 +51,7 @@ func parseConfig() *Config {
|
|||||||
|
|
||||||
flag.StringSliceVar(&conf.Forwards, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
flag.StringSliceVar(&conf.Forwards, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
||||||
flag.StringVar(&conf.Strategy.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
flag.StringVar(&conf.Strategy.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||||
flag.StringVar(&conf.Strategy.Check, "check", "http://www.msftconnecttest.com/connecttest.txt#expect=200", "check=tcp[://HOST:PORT]: tcp port connect check\ncheck=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]\ncheck=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR\ncheck=disable: disable health check")
|
flag.StringVar(&conf.Strategy.Check, "check", "http://www.msftconnecttest.com/connecttest.txt#expect=200", "check=tcp[://HOST:PORT]: tcp port connect check\ncheck=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]\ncheck=https://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]\ncheck=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR\ncheck=disable: disable health check")
|
||||||
flag.IntVar(&conf.Strategy.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)")
|
flag.IntVar(&conf.Strategy.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)")
|
||||||
flag.IntVar(&conf.Strategy.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)")
|
flag.IntVar(&conf.Strategy.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)")
|
||||||
flag.IntVar(&conf.Strategy.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode")
|
flag.IntVar(&conf.Strategy.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode")
|
||||||
|
@ -193,6 +193,7 @@ maxfailures=3
|
|||||||
# Forwarder health check:
|
# Forwarder health check:
|
||||||
# check=tcp[://HOST:PORT]: tcp port connect check
|
# check=tcp[://HOST:PORT]: tcp port connect check
|
||||||
# check=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]
|
# check=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]
|
||||||
|
# check=https://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]
|
||||||
# check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR
|
# check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR
|
||||||
# check=disable: disable health check
|
# check=disable: disable health check
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||||
|
2
main.go
2
main.go
@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "0.15.1"
|
version = "0.15.2"
|
||||||
config = parseConfig()
|
config = parseConfig()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package rule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
@ -24,6 +26,9 @@ type tcpChecker struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newTcpChecker(addr string, timeout time.Duration) *tcpChecker {
|
func newTcpChecker(addr string, timeout time.Duration) *tcpChecker {
|
||||||
|
if _, port, _ := net.SplitHostPort(addr); port == "" {
|
||||||
|
addr = net.JoinHostPort(addr, "80")
|
||||||
|
}
|
||||||
return &tcpChecker{addr, timeout}
|
return &tcpChecker{addr, timeout}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,20 +39,33 @@ func (c *tcpChecker) Check(dialer proxy.Dialer) (time.Duration, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer rc.Close()
|
rc.Close()
|
||||||
|
|
||||||
return time.Since(startTime), nil
|
return time.Since(startTime), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpChecker struct {
|
type httpChecker struct {
|
||||||
addr string
|
addr string
|
||||||
uri string
|
uri string
|
||||||
expect string
|
expect string
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
serverName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHttpChecker(addr, uri, expect string, timeout time.Duration) *httpChecker {
|
func newHttpChecker(addr, uri, expect string, timeout time.Duration, withTLS bool) *httpChecker {
|
||||||
return &httpChecker{addr, uri, expect, timeout}
|
c := &httpChecker{addr: addr, uri: uri, expect: expect, timeout: timeout}
|
||||||
|
if _, p, _ := net.SplitHostPort(addr); p == "" {
|
||||||
|
if withTLS {
|
||||||
|
c.addr = net.JoinHostPort(addr, "443")
|
||||||
|
} else {
|
||||||
|
c.addr = net.JoinHostPort(addr, "80")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.serverName = c.addr[:strings.LastIndex(c.addr, ":")]
|
||||||
|
if withTLS {
|
||||||
|
c.tlsConfig = &tls.Config{ServerName: c.serverName}
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check implements the Checker interface.
|
// Check implements the Checker interface.
|
||||||
@ -57,6 +75,15 @@ func (c *httpChecker) Check(dialer proxy.Dialer) (time.Duration, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.tlsConfig != nil {
|
||||||
|
tlsConn := tls.Client(rc, c.tlsConfig)
|
||||||
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
|
tlsConn.Close()
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
rc = tlsConn
|
||||||
|
}
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
|
|
||||||
if c.timeout > 0 {
|
if c.timeout > 0 {
|
||||||
@ -64,7 +91,7 @@ func (c *httpChecker) Check(dialer proxy.Dialer) (time.Duration, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err = io.WriteString(rc,
|
if _, err = io.WriteString(rc,
|
||||||
"GET "+c.uri+" HTTP/1.1\r\nHost:"+c.addr+"\r\nConnection: close"+"\r\n\r\n"); err != nil {
|
"GET "+c.uri+" HTTP/1.1\r\nHost:"+c.serverName+"\r\n\r\n"); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
// Config is config of rule.
|
// Config is config of rule.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Name string
|
RulePath string
|
||||||
|
|
||||||
Forward []string
|
Forward []string
|
||||||
Strategy Strategy
|
Strategy Strategy
|
||||||
@ -38,7 +38,7 @@ type Strategy struct {
|
|||||||
|
|
||||||
// NewConfFromFile returns a new config from file.
|
// NewConfFromFile returns a new config from file.
|
||||||
func NewConfFromFile(ruleFile string) (*Config, error) {
|
func NewConfFromFile(ruleFile string) (*Config, error) {
|
||||||
p := &Config{Name: ruleFile}
|
p := &Config{RulePath: ruleFile}
|
||||||
|
|
||||||
f := conflag.NewFromFile("rule", ruleFile)
|
f := conflag.NewFromFile("rule", ruleFile)
|
||||||
f.StringSliceUniqVar(&p.Forward, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
f.StringSliceUniqVar(&p.Forward, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -24,6 +25,7 @@ func (p priSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|||||||
|
|
||||||
// FwdrGroup is a forwarder group.
|
// FwdrGroup is a forwarder group.
|
||||||
type FwdrGroup struct {
|
type FwdrGroup struct {
|
||||||
|
name string
|
||||||
config *Strategy
|
config *Strategy
|
||||||
fwdrs priSlice
|
fwdrs priSlice
|
||||||
avail []*Forwarder // available forwarders
|
avail []*Forwarder // available forwarders
|
||||||
@ -34,7 +36,7 @@ type FwdrGroup struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFwdrGroup returns a new forward group.
|
// NewFwdrGroup returns a new forward group.
|
||||||
func NewFwdrGroup(name string, s []string, c *Strategy) *FwdrGroup {
|
func NewFwdrGroup(rulePath string, s []string, c *Strategy) *FwdrGroup {
|
||||||
var fwdrs []*Forwarder
|
var fwdrs []*Forwarder
|
||||||
for _, chain := range s {
|
for _, chain := range s {
|
||||||
fwdr, err := ForwarderFromURL(chain, c.IntFace,
|
fwdr, err := ForwarderFromURL(chain, c.IntFace,
|
||||||
@ -57,12 +59,13 @@ func NewFwdrGroup(name string, s []string, c *Strategy) *FwdrGroup {
|
|||||||
c.Strategy = "rr"
|
c.Strategy = "rr"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
name := strings.TrimSuffix(filepath.Base(rulePath), filepath.Ext(rulePath))
|
||||||
return newFwdrGroup(name, fwdrs, c)
|
return newFwdrGroup(name, fwdrs, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newFwdrGroup returns a new FwdrGroup.
|
// newFwdrGroup returns a new FwdrGroup.
|
||||||
func newFwdrGroup(name string, fwdrs []*Forwarder, c *Strategy) *FwdrGroup {
|
func newFwdrGroup(name string, fwdrs []*Forwarder, c *Strategy) *FwdrGroup {
|
||||||
p := &FwdrGroup{fwdrs: fwdrs, config: c}
|
p := &FwdrGroup{name: name, fwdrs: fwdrs, config: c}
|
||||||
sort.Sort(p.fwdrs)
|
sort.Sort(p.fwdrs)
|
||||||
|
|
||||||
p.init()
|
p.init()
|
||||||
@ -164,8 +167,8 @@ func (p *FwdrGroup) onStatusChanged(fwdr *Forwarder) {
|
|||||||
} else if fwdr.Priority() > p.Priority() {
|
} else if fwdr.Priority() > p.Priority() {
|
||||||
p.init()
|
p.init()
|
||||||
}
|
}
|
||||||
log.F("[group] %s(%d) changed status from DISABLED to ENABLED (%d of %d currently enabled)",
|
log.F("[group] %s: %s(%d) changed status from DISABLED to ENABLED (%d of %d currently enabled)",
|
||||||
fwdr.Addr(), fwdr.Priority(), len(p.avail), len(p.fwdrs))
|
p.name, fwdr.Addr(), fwdr.Priority(), len(p.avail), len(p.fwdrs))
|
||||||
} else {
|
} else {
|
||||||
for i, f := range p.avail {
|
for i, f := range p.avail {
|
||||||
if f == fwdr {
|
if f == fwdr {
|
||||||
@ -173,8 +176,8 @@ func (p *FwdrGroup) onStatusChanged(fwdr *Forwarder) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.F("[group] %s(%d) changed status from ENABLED to DISABLED (%d of %d currently enabled)",
|
log.F("[group] %s: %s(%d) changed status from ENABLED to DISABLED (%d of %d currently enabled)",
|
||||||
fwdr.Addr(), fwdr.Priority(), len(p.avail), len(p.fwdrs))
|
p.name, fwdr.Addr(), fwdr.Priority(), len(p.avail), len(p.fwdrs))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.avail) == 0 {
|
if len(p.avail) == 0 {
|
||||||
@ -185,7 +188,7 @@ func (p *FwdrGroup) onStatusChanged(fwdr *Forwarder) {
|
|||||||
// Check runs the forwarder checks.
|
// Check runs the forwarder checks.
|
||||||
func (p *FwdrGroup) Check() {
|
func (p *FwdrGroup) Check() {
|
||||||
if len(p.fwdrs) == 1 {
|
if len(p.fwdrs) == 1 {
|
||||||
log.F("[group] only 1 forwarder found, disable health checking")
|
log.F("[group] %s: only 1 forwarder found, disable health checking", p.name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,36 +198,32 @@ func (p *FwdrGroup) Check() {
|
|||||||
|
|
||||||
u, err := url.Parse(p.config.Check)
|
u, err := url.Parse(p.config.Check)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[group] parse check config error: %s, disable health checking", err)
|
log.F("[group] %s: parse check config error: %s, disable health checking", p.name, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := u.Host
|
addr := u.Host
|
||||||
if _, port, _ := net.SplitHostPort(addr); port == "" {
|
|
||||||
addr = net.JoinHostPort(addr, "80")
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := time.Duration(p.config.CheckTimeout) * time.Second
|
timeout := time.Duration(p.config.CheckTimeout) * time.Second
|
||||||
|
|
||||||
var checker Checker
|
var checker Checker
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
checker = newTcpChecker(addr, timeout)
|
checker = newTcpChecker(addr, timeout)
|
||||||
case "http":
|
case "http", "https":
|
||||||
expect := "HTTP" // default: check the first 4 chars in response
|
expect := "HTTP" // default: check the first 4 chars in response
|
||||||
params, _ := url.ParseQuery(u.Fragment)
|
params, _ := url.ParseQuery(u.Fragment)
|
||||||
if ex := params.Get("expect"); ex != "" {
|
if ex := params.Get("expect"); ex != "" {
|
||||||
expect = ex
|
expect = ex
|
||||||
}
|
}
|
||||||
checker = newHttpChecker(addr, u.RequestURI(), expect, timeout)
|
checker = newHttpChecker(addr, u.RequestURI(), expect, timeout, u.Scheme == "https")
|
||||||
case "file":
|
case "file":
|
||||||
checker = newFileChecker(u.Host + u.Path)
|
checker = newFileChecker(u.Host + u.Path)
|
||||||
default:
|
default:
|
||||||
log.F("[group] check config `%s`, disable health checking", p.config.Check)
|
log.F("[group] %s: unknown scheme in check config `%s`, disable health checking", p.name, p.config.Check)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.F("[group] using check config: %s", p.config.Check)
|
log.F("[group] %s: using check config: %s", p.name, p.config.Check)
|
||||||
|
|
||||||
for i := 0; i < len(p.fwdrs); i++ {
|
for i := 0; i < len(p.fwdrs); i++ {
|
||||||
go p.check(p.fwdrs[i], checker)
|
go p.check(p.fwdrs[i], checker)
|
||||||
@ -251,7 +250,7 @@ func (p *FwdrGroup) check(fwdr *Forwarder, checker Checker) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, proxy.ErrNotSupported) {
|
if errors.Is(err, proxy.ErrNotSupported) {
|
||||||
fwdr.SetMaxFailures(0)
|
fwdr.SetMaxFailures(0)
|
||||||
log.F("[check] %s(%d), %s, stop checking", fwdr.Addr(), fwdr.Priority(), err)
|
log.F("[check] %s: %s(%d), %s, stop checking", p.name, fwdr.Addr(), fwdr.Priority(), err)
|
||||||
fwdr.Enable()
|
fwdr.Enable()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -261,14 +260,15 @@ func (p *FwdrGroup) check(fwdr *Forwarder, checker Checker) {
|
|||||||
wait = 16
|
wait = 16
|
||||||
}
|
}
|
||||||
|
|
||||||
log.F("[check] %s(%d), FAILED. error: %s", fwdr.Addr(), fwdr.Priority(), err)
|
log.F("[check] %s: %s(%d), FAILED. error: %s", p.name, fwdr.Addr(), fwdr.Priority(), err)
|
||||||
fwdr.Disable()
|
fwdr.Disable()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
wait = 1
|
wait = 1
|
||||||
fwdr.SetLatency(int64(elapsed))
|
fwdr.SetLatency(int64(elapsed))
|
||||||
log.F("[check] %s(%d), SUCCESS. elapsed: %s", fwdr.Addr(), fwdr.Priority(), elapsed)
|
log.F("[check] %s: %s(%d), SUCCESS. elapsed: %s",
|
||||||
|
p.name, fwdr.Addr(), fwdr.Priority(), elapsed)
|
||||||
fwdr.Enable()
|
fwdr.Enable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ func NewProxy(mainForwarders []string, mainStrategy *Strategy, rules []*Config)
|
|||||||
rd := &Proxy{main: NewFwdrGroup("main", mainForwarders, mainStrategy)}
|
rd := &Proxy{main: NewFwdrGroup("main", mainForwarders, mainStrategy)}
|
||||||
|
|
||||||
for _, r := range rules {
|
for _, r := range rules {
|
||||||
group := NewFwdrGroup(r.Name, r.Forward, &r.Strategy)
|
group := NewFwdrGroup(r.RulePath, r.Forward, &r.Strategy)
|
||||||
rd.all = append(rd.all, group)
|
rd.all = append(rd.all, group)
|
||||||
|
|
||||||
for _, domain := range r.Domain {
|
for _, domain := range r.Domain {
|
||||||
|
Loading…
Reference in New Issue
Block a user