return expected status through Rest API and clean useless code

This commit is contained in:
wzdnzd 2023-12-01 16:44:30 +08:00 committed by Larvan2
parent 3b57a923fd
commit cc6429722a
10 changed files with 68 additions and 91 deletions

View file

@ -17,8 +17,6 @@ import (
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
) )
@ -41,11 +39,6 @@ type Proxy struct {
extra *xsync.MapOf[string, *extraProxyState] extra *xsync.MapOf[string, *extraProxyState]
} }
// Alive implements C.Proxy
func (p *Proxy) Alive() bool {
return p.alive.Load()
}
// AliveForTestUrl implements C.Proxy // AliveForTestUrl implements C.Proxy
func (p *Proxy) AliveForTestUrl(url string) bool { func (p *Proxy) AliveForTestUrl(url string) bool {
if state, ok := p.extra.Load(url); ok { if state, ok := p.extra.Load(url); ok {
@ -181,7 +174,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
_ = json.Unmarshal(inner, &mapping) _ = json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory() mapping["history"] = p.DelayHistory()
mapping["extra"] = p.ExtraDelayHistory() mapping["extra"] = p.ExtraDelayHistory()
mapping["alive"] = p.Alive() mapping["alive"] = p.AliveForTestUrl(p.url)
mapping["name"] = p.Name() mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP() mapping["udp"] = p.SupportUDP()
mapping["xudp"] = p.SupportXUDP() mapping["xudp"] = p.SupportXUDP()
@ -191,13 +184,11 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
// URLTest get the delay for the specified URL // URLTest get the delay for the specified URL
// implements C.Proxy // implements C.Proxy
func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store C.DelayHistoryStoreType) (t uint16, err error) { func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (t uint16, err error) {
defer func() { defer func() {
alive := err == nil alive := err == nil
store = p.determineFinalStoreType(store, url)
switch store { if len(p.url) == 0 || url == p.url {
case C.OriginalHistory:
p.alive.Store(alive) p.alive.Store(alive)
record := C.DelayHistory{Time: time.Now()} record := C.DelayHistory{Time: time.Now()}
if alive { if alive {
@ -212,7 +203,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
if len(p.url) == 0 { if len(p.url) == 0 {
p.url = url p.url = url
} }
case C.ExtraHistory: } else {
record := C.DelayHistory{Time: time.Now()} record := C.DelayHistory{Time: time.Now()}
if alive { if alive {
record.Delay = t record.Delay = t
@ -236,8 +227,6 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
if state.history.Len() > defaultHistoriesNum { if state.history.Len() > defaultHistoriesNum {
state.history.Pop() state.history.Pop()
} }
default:
log.Debugln("health check result will be discarded, url: %s alive: %t, delay: %d", url, alive, t)
} }
}() }()
@ -349,24 +338,3 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
} }
return return
} }
func (p *Proxy) determineFinalStoreType(store C.DelayHistoryStoreType, url string) C.DelayHistoryStoreType {
if store != C.DropHistory {
return store
}
if len(p.url) == 0 || url == p.url {
return C.OriginalHistory
}
if p.extra.Size() < 2*C.DefaultMaxHealthCheckUrlNum {
return C.ExtraHistory
}
_, ok := p.extra.Load(url)
if ok {
return C.ExtraHistory
}
return store
}

View file

@ -88,7 +88,7 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
"now": f.Now(), "now": f.Now(),
"all": all, "all": all,
"testUrl": f.testUrl, "testUrl": f.testUrl,
"expected": f.expectedStatus, "expectedStatus": f.expectedStatus,
}) })
} }
@ -102,13 +102,11 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
proxies := f.GetProxies(touch) proxies := f.GetProxies(touch)
for _, proxy := range proxies { for _, proxy := range proxies {
if len(f.selected) == 0 { if len(f.selected) == 0 {
// if proxy.Alive() {
if proxy.AliveForTestUrl(f.testUrl) { if proxy.AliveForTestUrl(f.testUrl) {
return proxy return proxy
} }
} else { } else {
if proxy.Name() == f.selected { if proxy.Name() == f.selected {
// if proxy.Alive() {
if proxy.AliveForTestUrl(f.testUrl) { if proxy.AliveForTestUrl(f.testUrl) {
return proxy return proxy
} else { } else {
@ -135,12 +133,11 @@ func (f *Fallback) Set(name string) error {
} }
f.selected = name f.selected = name
// if !p.Alive() {
if !p.AliveForTestUrl(f.testUrl) { if !p.AliveForTestUrl(f.testUrl) {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000)) ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
defer cancel() defer cancel()
expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus) expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus)
_, _ = p.URLTest(ctx, f.testUrl, expectedStatus, C.ExtraHistory) _, _ = p.URLTest(ctx, f.testUrl, expectedStatus)
} }
return nil return nil

View file

@ -202,7 +202,7 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus uti
proxy := proxy proxy := proxy
wg.Add(1) wg.Add(1)
go func() { go func() {
delay, err := proxy.URLTest(ctx, url, expectedStatus, C.DropHistory) delay, err := proxy.URLTest(ctx, url, expectedStatus)
if err == nil { if err == nil {
lock.Lock() lock.Lock()
mp[proxy.Name()] = delay mp[proxy.Name()] = delay

View file

@ -95,7 +95,8 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
// select don't need health check // select don't need health check
if groupOption.Type != "select" && groupOption.Type != "relay" { if groupOption.Type != "select" && groupOption.Type != "relay" {
if groupOption.URL == "" { if groupOption.URL == "" {
groupOption.URL = "https://cp.cloudflare.com/generate_204" groupOption.URL = C.DefaultTestURL
testUrl = groupOption.URL
} }
if groupOption.Interval == 0 { if groupOption.Interval == 0 {

View file

@ -101,7 +101,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
proxies := u.GetProxies(touch) proxies := u.GetProxies(touch)
if u.selected != "" { if u.selected != "" {
for _, proxy := range proxies { for _, proxy := range proxies {
if !proxy.Alive() { if !proxy.AliveForTestUrl(u.testUrl) {
continue continue
} }
if proxy.Name() == u.selected { if proxy.Name() == u.selected {
@ -113,7 +113,6 @@ func (u *URLTest) fast(touch bool) C.Proxy {
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) { elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
fast := proxies[0] fast := proxies[0]
// min := fast.LastDelay()
min := fast.LastDelayForTestUrl(u.testUrl) min := fast.LastDelayForTestUrl(u.testUrl)
fastNotExist := true fastNotExist := true
@ -122,12 +121,10 @@ func (u *URLTest) fast(touch bool) C.Proxy {
fastNotExist = false fastNotExist = false
} }
// if !proxy.Alive() {
if !proxy.AliveForTestUrl(u.testUrl) { if !proxy.AliveForTestUrl(u.testUrl) {
continue continue
} }
// delay := proxy.LastDelay()
delay := proxy.LastDelayForTestUrl(u.testUrl) delay := proxy.LastDelayForTestUrl(u.testUrl)
if delay < min { if delay < min {
fast = proxy fast = proxy
@ -136,7 +133,6 @@ func (u *URLTest) fast(touch bool) C.Proxy {
} }
// tolerance // tolerance
// if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
if u.fastNode == nil || fastNotExist || !u.fastNode.AliveForTestUrl(u.testUrl) || u.fastNode.LastDelayForTestUrl(u.testUrl) > fast.LastDelayForTestUrl(u.testUrl)+u.tolerance { if u.fastNode == nil || fastNotExist || !u.fastNode.AliveForTestUrl(u.testUrl) || u.fastNode.LastDelayForTestUrl(u.testUrl) > fast.LastDelayForTestUrl(u.testUrl)+u.tolerance {
u.fastNode = fast u.fastNode = fast
} }
@ -173,7 +169,7 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
"now": u.Now(), "now": u.Now(),
"all": all, "all": all,
"testUrl": u.testUrl, "testUrl": u.testUrl,
"expected": u.expectedStatus, "expectedStatus": u.expectedStatus,
}) })
} }

View file

@ -18,7 +18,6 @@ import (
const ( const (
defaultURLTestTimeout = time.Second * 5 defaultURLTestTimeout = time.Second * 5
defaultURLTestURL = "https://www.gstatic.com/generate_204"
) )
type HealthCheckOption struct { type HealthCheckOption struct {
@ -105,12 +104,6 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.
return return
} }
// due to the time-consuming nature of health checks, a maximum of defaultMaxTestURLNum URLs can be set for testing
if len(hc.extra) > C.DefaultMaxHealthCheckUrlNum {
log.Debugln("skip add url: %s to health check because it has reached the maximum limit: %d", url, C.DefaultMaxHealthCheckUrlNum)
return
}
option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus} option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus}
splitAndAddFiltersToExtra(filter, option) splitAndAddFiltersToExtra(filter, option)
hc.extra[url] = option hc.extra[url] = option
@ -182,13 +175,8 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
} }
var filterReg *regexp2.Regexp var filterReg *regexp2.Regexp
var store = C.OriginalHistory
var expectedStatus utils.IntRanges[uint16] var expectedStatus utils.IntRanges[uint16]
if option != nil { if option != nil {
if url != hc.url {
store = C.ExtraHistory
}
expectedStatus = option.expectedStatus expectedStatus = option.expectedStatus
if len(option.filters) != 0 { if len(option.filters) != 0 {
filters := make([]string, 0, len(option.filters)) filters := make([]string, 0, len(option.filters))
@ -213,7 +201,7 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
defer cancel() defer cancel()
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid) log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
_, _ = p.URLTest(ctx, url, expectedStatus, store) _, _ = p.URLTest(ctx, url, expectedStatus)
log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid) log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid)
return false, nil return false, nil
}) })
@ -228,7 +216,7 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, exp
if len(url) == 0 { if len(url) == 0 {
interval = 0 interval = 0
expectedStatus = nil expectedStatus = nil
url = defaultURLTestURL url = C.DefaultTestURL
} }
return &HealthCheck{ return &HealthCheck{

View file

@ -48,12 +48,18 @@ type proxySetProvider struct {
} }
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) { func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
expectedStatus := "*"
if pp.healthCheck.expectedStatus != nil {
expectedStatus = pp.healthCheck.expectedStatus.ToString()
}
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
"name": pp.Name(), "name": pp.Name(),
"type": pp.Type().String(), "type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(), "vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(), "proxies": pp.Proxies(),
"testUrl": pp.healthCheck.url, "testUrl": pp.healthCheck.url,
"expectedStatus": expectedStatus,
"updatedAt": pp.UpdatedAt, "updatedAt": pp.UpdatedAt,
"subscriptionInfo": pp.subscriptionInfo, "subscriptionInfo": pp.subscriptionInfo,
}) })
@ -214,12 +220,18 @@ type compatibleProvider struct {
} }
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
expectedStatus := "*"
if cp.healthCheck.expectedStatus != nil {
expectedStatus = cp.healthCheck.expectedStatus.ToString()
}
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
"name": cp.Name(), "name": cp.Name(),
"type": cp.Type().String(), "type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(), "vehicleType": cp.VehicleType().String(),
"proxies": cp.Proxies(), "proxies": cp.Proxies(),
"testUrl": cp.healthCheck.url, "testUrl": cp.healthCheck.url,
"expectedStatus": expectedStatus,
}) })
} }

View file

@ -75,3 +75,26 @@ func (ranges IntRanges[T]) Check(status T) bool {
return false return false
} }
func (ranges IntRanges[T]) ToString() string {
if len(ranges) == 0 {
return "*"
}
terms := make([]string, len(ranges))
for i, r := range ranges {
start := r.Start()
end := r.End()
var term string
if start == end {
term = strconv.Itoa(int(start))
} else {
term = strconv.Itoa(int(start)) + "-" + strconv.Itoa(int(end))
}
terms[i] = term
}
return strings.Join(terms, "/")
}

View file

@ -47,7 +47,7 @@ const (
DefaultDropTime = 12 * DefaultTCPTimeout DefaultDropTime = 12 * DefaultTCPTimeout
DefaultUDPTimeout = DefaultTCPTimeout DefaultUDPTimeout = DefaultTCPTimeout
DefaultTLSTimeout = DefaultTCPTimeout DefaultTLSTimeout = DefaultTCPTimeout
DefaultMaxHealthCheckUrlNum = 16 DefaultTestURL = "https://cp.cloudflare.com/generate_204"
) )
var ErrNotSupport = errors.New("no support") var ErrNotSupport = errors.New("no support")
@ -149,21 +149,13 @@ type DelayHistory struct {
type DelayHistoryStoreType int type DelayHistoryStoreType int
const (
OriginalHistory DelayHistoryStoreType = iota
ExtraHistory
DropHistory
)
type Proxy interface { type Proxy interface {
ProxyAdapter ProxyAdapter
Alive() bool
AliveForTestUrl(url string) bool AliveForTestUrl(url string) bool
DelayHistory() []DelayHistory DelayHistory() []DelayHistory
ExtraDelayHistory() map[string][]DelayHistory ExtraDelayHistory() map[string][]DelayHistory
LastDelay() uint16
LastDelayForTestUrl(url string) uint16 LastDelayForTestUrl(url string) uint16
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store DelayHistoryStoreType) (uint16, error) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (uint16, error)
// Deprecated: use DialContext instead. // Deprecated: use DialContext instead.
Dial(metadata *Metadata) (Conn, error) Dial(metadata *Metadata) (Conn, error)

View file

@ -125,7 +125,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout)) ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
defer cancel() defer cancel()
delay, err := proxy.URLTest(ctx, url, expectedStatus, C.ExtraHistory) delay, err := proxy.URLTest(ctx, url, expectedStatus)
if ctx.Err() != nil { if ctx.Err() != nil {
render.Status(r, http.StatusGatewayTimeout) render.Status(r, http.StatusGatewayTimeout)
render.JSON(w, r, ErrRequestTimeout) render.JSON(w, r, ErrRequestTimeout)