feat: proxies group URLTest api

This commit is contained in:
adlyq 2022-05-30 21:55:09 +08:00
parent 58d299c737
commit 4092a7c84b
7 changed files with 127 additions and 9 deletions

View File

@ -4,6 +4,9 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/Dreamacro/clash/common/queue"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
@ -11,10 +14,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/common/queue"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"go.uber.org/atomic" "go.uber.org/atomic"
) )

View File

@ -51,6 +51,10 @@ func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, errors.New("no support") return c, errors.New("no support")
} }
func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
return nil, errors.New("no support")
}
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return nil, errors.New("no support") return nil, errors.New("no support")

View File

@ -1,6 +1,8 @@
package outboundgroup package outboundgroup
import ( import (
"context"
"fmt"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
@ -105,6 +107,34 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
return proxies return proxies
} }
func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16, error) {
var wg sync.WaitGroup
var lock sync.Mutex
mp := map[string]uint16{}
proxies := gb.GetProxies(false)
for _, proxy := range proxies {
proxy := proxy
wg.Add(1)
go func() {
delay, err := proxy.URLTest(ctx, url)
lock.Lock()
if err == nil {
mp[proxy.Name()] = delay
}
lock.Unlock()
wg.Done()
}()
}
wg.Wait()
if len(mp) == 0 {
return mp, fmt.Errorf("get delay: all proxies timeout")
} else {
return mp, nil
}
}
func (gb *GroupBase) onDialFailed() { func (gb *GroupBase) onDialFailed() {
if gb.failedTesting.Load() { if gb.failedTesting.Load() {
return return

View File

@ -108,6 +108,11 @@ type ProxyAdapter interface {
Unwrap(metadata *Metadata) Proxy Unwrap(metadata *Metadata) Proxy
} }
type Group interface {
URLTest(ctx context.Context, url string) (mp map[string]uint16, err error)
GetProxies(touch bool) []Proxy
}
type DelayHistory struct { type DelayHistory struct {
Time time.Time `json:"time"` Time time.Time `json:"time"`
Delay uint16 `json:"delay"` Delay uint16 `json:"delay"`

View File

@ -1,7 +1,7 @@
package provider package provider
import ( import (
"github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// Vehicle Type // Vehicle Type
@ -65,10 +65,10 @@ type Provider interface {
// ProxyProvider interface // ProxyProvider interface
type ProxyProvider interface { type ProxyProvider interface {
Provider Provider
Proxies() []constant.Proxy Proxies() []C.Proxy
// ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies. // ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies.
// Commonly used in DialContext and DialPacketConn // Commonly used in DialContext and DialPacketConn
ProxiesWithTouch() []constant.Proxy ProxiesWithTouch() []C.Proxy
HealthCheck() HealthCheck()
Version() uint Version() uint
} }
@ -100,7 +100,7 @@ func (rt RuleType) String() string {
type RuleProvider interface { type RuleProvider interface {
Provider Provider
Behavior() RuleType Behavior() RuleType
Match(*constant.Metadata) bool Match(*C.Metadata) bool
ShouldResolveIP() bool ShouldResolveIP() bool
AsRule(adaptor string) constant.Rule AsRule(adaptor string) C.Rule
} }

79
hub/route/groups.go Normal file
View File

@ -0,0 +1,79 @@
package route
import (
"context"
"github.com/Dreamacro/clash/adapter"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"net/http"
"strconv"
"time"
)
func GroupRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getGroups)
r.Route("/{name}", func(r chi.Router) {
r.Use(parseProxyName, findProxyByName)
r.Get("/", getGroup)
r.Get("/delay", getGroupDelay)
})
return r
}
func getGroups(w http.ResponseWriter, r *http.Request) {
var gs []C.Proxy
for _, p := range tunnel.Proxies() {
if _, ok := p.(*adapter.Proxy).ProxyAdapter.(C.Group); ok {
gs = append(gs, p)
}
}
render.JSON(w, r, render.M{
"proxies": gs,
})
}
func getGroup(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
if _, ok := proxy.(*adapter.Proxy).ProxyAdapter.(C.Group); ok {
render.JSON(w, r, proxy)
return
}
render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound)
}
func getGroupDelay(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
group, ok := proxy.(*adapter.Proxy).ProxyAdapter.(C.Group)
if !ok {
render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound)
return
}
query := r.URL.Query()
url := query.Get("url")
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32)
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
defer cancel()
dm, err := group.URLTest(ctx, url)
if err != nil {
render.Status(r, http.StatusGatewayTimeout)
render.JSON(w, r, newError(err.Error()))
return
}
render.JSON(w, r, dm)
}

View File

@ -68,6 +68,7 @@ func Start(addr string, secret string) {
r.Get("/version", version) r.Get("/version", version)
r.Mount("/configs", configRouter()) r.Mount("/configs", configRouter())
r.Mount("/proxies", proxyRouter()) r.Mount("/proxies", proxyRouter())
r.Mount("/group", GroupRouter())
r.Mount("/rules", ruleRouter()) r.Mount("/rules", ruleRouter())
r.Mount("/connections", connectionRouter()) r.Mount("/connections", connectionRouter())
r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/proxies", proxyProviderRouter())