Feature: add basic api for proxy provider
This commit is contained in:
parent
29cf3ca0ef
commit
95e9ae2d8d
6 changed files with 129 additions and 10 deletions
|
@ -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
17
hub/route/common.go
Normal 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
|
||||||
|
}
|
|
@ -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
66
hub/route/provider.go
Normal 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))
|
||||||
|
})
|
||||||
|
}
|
|
@ -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))
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 != "" {
|
||||||
|
|
Loading…
Reference in a new issue