Feature: add basic api for proxy provider

This commit is contained in:
Dreamacro 2019-12-11 17:31:15 +08:00
parent 29cf3ca0ef
commit 95e9ae2d8d
6 changed files with 129 additions and 10 deletions

View file

@ -3,6 +3,7 @@ package provider
import ( import (
"bytes" "bytes"
"crypto/md5" "crypto/md5"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -58,6 +59,7 @@ type Provider interface {
type ProxyProvider interface { type ProxyProvider interface {
Provider Provider
Proxies() []C.Proxy Proxies() []C.Proxy
HealthCheck()
} }
type ProxySchema struct { type ProxySchema struct {
@ -72,11 +74,22 @@ type ProxySetProvider struct {
healthCheck *healthCheck healthCheck *healthCheck
healthCheckOption *HealthCheckOption healthCheckOption *HealthCheckOption
ticker *time.Ticker ticker *time.Ticker
updatedAt *time.Time
// mux for avoiding creating new goroutines when pulling // mux for avoiding creating new goroutines when pulling
mux sync.Mutex mux sync.Mutex
} }
func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": pp.Name(),
"type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(),
"updatedAt": pp.updatedAt,
})
}
func (pp *ProxySetProvider) Name() string { func (pp *ProxySetProvider) Name() string {
return pp.name return pp.name
} }
@ -85,6 +98,14 @@ func (pp *ProxySetProvider) Reload() error {
return nil return nil
} }
func (pp *ProxySetProvider) HealthCheck() {
pp.mux.Lock()
defer pp.mux.Unlock()
if pp.healthCheck != nil {
pp.healthCheck.check()
}
}
func (pp *ProxySetProvider) Destroy() error { func (pp *ProxySetProvider) Destroy() error {
pp.mux.Lock() pp.mux.Lock()
defer pp.mux.Unlock() defer pp.mux.Unlock()
@ -175,6 +196,8 @@ func (pp *ProxySetProvider) pull() error {
return err return err
} }
now := time.Now()
pp.updatedAt = &now
pp.hash = hash pp.hash = hash
pp.setProxies(proxies) pp.setProxies(proxies)
@ -210,9 +233,9 @@ func (pp *ProxySetProvider) setProxies(proxies []C.Proxy) {
pp.mux.Lock() pp.mux.Lock()
if pp.healthCheck != nil { if pp.healthCheck != nil {
pp.healthCheck.close() pp.healthCheck.close()
pp.healthCheck = newHealthCheck(proxies, pp.healthCheckOption.URL, pp.healthCheckOption.Interval)
go pp.healthCheck.process()
} }
pp.healthCheck = newHealthCheck(proxies, pp.healthCheckOption.URL, pp.healthCheckOption.Interval)
go pp.healthCheck.process()
pp.mux.Unlock() pp.mux.Unlock()
} }
} }
@ -238,6 +261,15 @@ type CompatibleProvier struct {
proxies []C.Proxy proxies []C.Proxy
} }
func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": cp.Name(),
"type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(),
"proxies": cp.Proxies(),
})
}
func (cp *CompatibleProvier) Name() string { func (cp *CompatibleProvier) Name() string {
return cp.name return cp.name
} }
@ -253,6 +285,12 @@ func (cp *CompatibleProvier) Destroy() error {
return nil return nil
} }
func (cp *CompatibleProvier) HealthCheck() {
if cp.healthCheck != nil {
cp.healthCheck.check()
}
}
func (cp *CompatibleProvier) Initial() error { func (cp *CompatibleProvier) Initial() error {
if cp.healthCheck != nil { if cp.healthCheck != nil {
go cp.healthCheck.process() go cp.healthCheck.process()

17
hub/route/common.go Normal file
View file

@ -0,0 +1,17 @@
package route
import (
"net/http"
"net/url"
"github.com/go-chi/chi"
)
// When name is composed of a partial escape string, Golang does not unescape it
func getEscapeParam(r *http.Request, paramName string) string {
param := chi.URLParam(r, paramName)
if newParam, err := url.PathUnescape(param); err == nil {
param = newParam
}
return param
}

View file

@ -1,8 +1,10 @@
package route package route
var ( var (
CtxKeyProxyName = contextKey("proxy name") CtxKeyProxyName = contextKey("proxy name")
CtxKeyProxy = contextKey("proxy") CtxKeyProviderName = contextKey("provider name")
CtxKeyProxy = contextKey("proxy")
CtxKeyProvider = contextKey("provider")
) )
type contextKey string type contextKey string

66
hub/route/provider.go Normal file
View file

@ -0,0 +1,66 @@
package route
import (
"context"
"net/http"
"github.com/Dreamacro/clash/adapters/provider"
T "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi"
"github.com/go-chi/render"
)
func proxyProviderRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getProviders)
r.Route("/{name}", func(r chi.Router) {
r.Use(parseProviderName, findProviderByName)
r.Get("/", getProvider)
r.Get("/healthcheck", doProviderHealthCheck)
})
return r
}
func getProviders(w http.ResponseWriter, r *http.Request) {
providers := T.Instance().Providers()
render.JSON(w, r, render.M{
"providers": providers,
})
}
func getProvider(w http.ResponseWriter, r *http.Request) {
provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider)
render.JSON(w, r, provider)
}
func doProviderHealthCheck(w http.ResponseWriter, r *http.Request) {
provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider)
provider.HealthCheck()
render.NoContent(w, r)
}
func parseProviderName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := getEscapeParam(r, "name")
ctx := context.WithValue(r.Context(), CtxKeyProviderName, name)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func findProviderByName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := r.Context().Value(CtxKeyProviderName).(string)
providers := T.Instance().Providers()
provider, exist := providers[name]
if !exist {
render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound)
return
}
ctx := context.WithValue(r.Context(), CtxKeyProvider, provider)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

View file

@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"time" "time"
@ -30,13 +29,9 @@ func proxyRouter() http.Handler {
return r return r
} }
// When name is composed of a partial escape string, Golang does not unescape it
func parseProxyName(next http.Handler) http.Handler { func parseProxyName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name") name := getEscapeParam(r, "name")
if newName, err := url.PathUnescape(name); err == nil {
name = newName
}
ctx := context.WithValue(r.Context(), CtxKeyProxyName, name) ctx := context.WithValue(r.Context(), CtxKeyProxyName, name)
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })

View file

@ -68,6 +68,7 @@ func Start(addr string, secret string) {
r.Mount("/proxies", proxyRouter()) r.Mount("/proxies", proxyRouter())
r.Mount("/rules", ruleRouter()) r.Mount("/rules", ruleRouter())
r.Mount("/connections", connectionRouter()) r.Mount("/connections", connectionRouter())
r.Mount("/providers/proxies", proxyProviderRouter())
}) })
if uiPath != "" { if uiPath != "" {