Chore: improve code architecture
This commit is contained in:
@ -1,33 +0,0 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/proxy"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
var (
|
||||
tunnel = T.Instance()
|
||||
cfg = config.Instance()
|
||||
listener = proxy.Instance()
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type Errors struct {
|
||||
Errors map[string]string `json:"errors"`
|
||||
}
|
||||
|
||||
func formatErrors(errorsMap map[string]error) (bool, Errors) {
|
||||
errors := make(map[string]string)
|
||||
hasError := false
|
||||
for key, err := range errorsMap {
|
||||
if err != nil {
|
||||
errors[key] = err.Error()
|
||||
hasError = true
|
||||
}
|
||||
}
|
||||
return hasError, Errors{Errors: errors}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func configRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getConfigs)
|
||||
r.Put("/", updateConfigs)
|
||||
return r
|
||||
}
|
||||
|
||||
type configSchema struct {
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socket-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
Mode string `json:"mode"`
|
||||
LogLevel string `json:"log-level"`
|
||||
}
|
||||
|
||||
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
general := cfg.General()
|
||||
render.JSON(w, r, configSchema{
|
||||
Port: general.Port,
|
||||
SocksPort: general.SocksPort,
|
||||
RedirPort: general.RedirPort,
|
||||
AllowLan: general.AllowLan,
|
||||
Mode: general.Mode.String(),
|
||||
LogLevel: general.LogLevel.String(),
|
||||
})
|
||||
}
|
||||
|
||||
func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
general := &C.General{}
|
||||
err := render.DecodeJSON(r.Body, general)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Format error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// update errors
|
||||
var modeErr, logLevelErr error
|
||||
|
||||
// update mode
|
||||
if general.Mode != nil {
|
||||
mode, ok := config.ModeMapping[*general.Mode]
|
||||
if !ok {
|
||||
modeErr = fmt.Errorf("Mode error")
|
||||
} else {
|
||||
cfg.SetMode(mode)
|
||||
}
|
||||
}
|
||||
|
||||
// update log-level
|
||||
if general.LogLevel != nil {
|
||||
level, ok := C.LogLevelMapping[*general.LogLevel]
|
||||
if !ok {
|
||||
logLevelErr = fmt.Errorf("Log Level error")
|
||||
} else {
|
||||
cfg.SetLogLevel(level)
|
||||
}
|
||||
}
|
||||
|
||||
hasError, errors := formatErrors(map[string]error{
|
||||
"mode": modeErr,
|
||||
"log-level": logLevelErr,
|
||||
})
|
||||
|
||||
if hasError {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, errors)
|
||||
return
|
||||
}
|
||||
|
||||
// update proxy
|
||||
cfg.UpdateProxy(config.ProxyConfig{
|
||||
AllowLan: general.AllowLan,
|
||||
Port: general.Port,
|
||||
SocksPort: general.SocksPort,
|
||||
RedirPort: general.RedirPort,
|
||||
})
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
66
hub/executor/executor.go
Normal file
66
hub/executor/executor.go
Normal file
@ -0,0 +1,66 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
P "github.com/Dreamacro/clash/proxy"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
// Parse config with default config path
|
||||
func Parse() (*config.Config, error) {
|
||||
return ParseWithPath(C.Path.Config())
|
||||
}
|
||||
|
||||
// ParseWithPath parse config with custom config path
|
||||
func ParseWithPath(path string) (*config.Config, error) {
|
||||
return config.Parse(path)
|
||||
}
|
||||
|
||||
// ApplyConfig dispatch configure to all parts
|
||||
func ApplyConfig(cfg *config.Config) {
|
||||
updateProxies(cfg.Proxies)
|
||||
updateRules(cfg.Rules)
|
||||
updateGeneral(cfg.General)
|
||||
}
|
||||
|
||||
func GetGeneral() *config.General {
|
||||
ports := P.GetPorts()
|
||||
return &config.General{
|
||||
Port: ports.Port,
|
||||
SocksPort: ports.SocksPort,
|
||||
RedirPort: ports.RedirPort,
|
||||
AllowLan: P.AllowLan(),
|
||||
Mode: T.Instance().Mode(),
|
||||
LogLevel: log.Level(),
|
||||
}
|
||||
}
|
||||
|
||||
func updateProxies(proxies map[string]C.Proxy) {
|
||||
T.Instance().UpdateProxies(proxies)
|
||||
}
|
||||
|
||||
func updateRules(rules []C.Rule) {
|
||||
T.Instance().UpdateRules(rules)
|
||||
}
|
||||
|
||||
func updateGeneral(general *config.General) {
|
||||
allowLan := general.AllowLan
|
||||
|
||||
P.SetAllowLan(allowLan)
|
||||
if err := P.ReCreateHTTP(general.Port); err != nil {
|
||||
log.Errorln("Start HTTP server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateSocks(general.SocksPort); err != nil {
|
||||
log.Errorln("Start SOCKS5 server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateRedir(general.RedirPort); err != nil {
|
||||
log.Errorln("Start Redir server error: %s", err.Error())
|
||||
}
|
||||
|
||||
log.SetLevel(general.LogLevel)
|
||||
T.Instance().SetMode(general.Mode)
|
||||
}
|
21
hub/hub.go
Normal file
21
hub/hub.go
Normal file
@ -0,0 +1,21 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
"github.com/Dreamacro/clash/hub/route"
|
||||
)
|
||||
|
||||
// Parse call at the beginning of clash
|
||||
func Parse() error {
|
||||
cfg, err := executor.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.General.ExternalController != "" {
|
||||
go route.Start(cfg.General.ExternalController, cfg.General.Secret)
|
||||
}
|
||||
|
||||
executor.ApplyConfig(cfg)
|
||||
return nil
|
||||
}
|
217
hub/proxies.go
217
hub/proxies.go
@ -1,217 +0,0 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
A "github.com/Dreamacro/clash/adapters/outbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func proxyRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getProxies)
|
||||
r.With(parseProxyName).Get("/{name}", getProxy)
|
||||
r.With(parseProxyName).Get("/{name}/delay", getProxyDelay)
|
||||
r.With(parseProxyName).Put("/{name}", updateProxy)
|
||||
return r
|
||||
}
|
||||
|
||||
// When name is composed of a partial escape string, Golang does not unescape it
|
||||
func parseProxyName(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
if newName, err := url.PathUnescape(name); err == nil {
|
||||
name = newName
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), contextKey("proxy name"), name)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
type SampleProxy struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type Selector struct {
|
||||
Type string `json:"type"`
|
||||
Now string `json:"now"`
|
||||
All []string `json:"all"`
|
||||
}
|
||||
|
||||
type URLTest struct {
|
||||
Type string `json:"type"`
|
||||
Now string `json:"now"`
|
||||
}
|
||||
|
||||
type Fallback struct {
|
||||
Type string `json:"type"`
|
||||
Now string `json:"now"`
|
||||
}
|
||||
|
||||
func transformProxy(proxy C.Proxy) interface{} {
|
||||
t := proxy.Type()
|
||||
switch t {
|
||||
case C.Selector:
|
||||
selector := proxy.(*A.Selector)
|
||||
return Selector{
|
||||
Type: t.String(),
|
||||
Now: selector.Now(),
|
||||
All: selector.All(),
|
||||
}
|
||||
case C.URLTest:
|
||||
return URLTest{
|
||||
Type: t.String(),
|
||||
Now: proxy.(*A.URLTest).Now(),
|
||||
}
|
||||
case C.Fallback:
|
||||
return Fallback{
|
||||
Type: t.String(),
|
||||
Now: proxy.(*A.Fallback).Now(),
|
||||
}
|
||||
default:
|
||||
return SampleProxy{
|
||||
Type: proxy.Type().String(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type GetProxiesResponse struct {
|
||||
Proxies map[string]interface{} `json:"proxies"`
|
||||
}
|
||||
|
||||
func getProxies(w http.ResponseWriter, r *http.Request) {
|
||||
rawProxies := cfg.Proxies()
|
||||
proxies := make(map[string]interface{})
|
||||
for name, proxy := range rawProxies {
|
||||
proxies[name] = transformProxy(proxy)
|
||||
}
|
||||
render.JSON(w, r, GetProxiesResponse{Proxies: proxies})
|
||||
}
|
||||
|
||||
func getProxy(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.Context().Value(contextKey("proxy name")).(string)
|
||||
proxies := cfg.Proxies()
|
||||
proxy, exist := proxies[name]
|
||||
if !exist {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Proxy not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
render.JSON(w, r, transformProxy(proxy))
|
||||
}
|
||||
|
||||
type UpdateProxyRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
req := UpdateProxyRequest{}
|
||||
if err := render.DecodeJSON(r.Body, &req); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Format error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
name := r.Context().Value(contextKey("proxy name")).(string)
|
||||
proxies := cfg.Proxies()
|
||||
proxy, exist := proxies[name]
|
||||
if !exist {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Proxy not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
selector, ok := proxy.(*A.Selector)
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Proxy can't update",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := selector.Set(req.Name); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, Error{
|
||||
Error: fmt.Sprintf("Selector update error: %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
type GetProxyDelayRequest struct {
|
||||
URL string `json:"url"`
|
||||
Timeout int16 `json:"timeout"`
|
||||
}
|
||||
|
||||
type GetProxyDelayResponse struct {
|
||||
Delay int16 `json:"delay"`
|
||||
}
|
||||
|
||||
func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
url := query.Get("url")
|
||||
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Format error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
name := r.Context().Value(contextKey("proxy name")).(string)
|
||||
proxies := cfg.Proxies()
|
||||
proxy, exist := proxies[name]
|
||||
if !exist {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Proxy not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
sigCh := make(chan int16)
|
||||
go func() {
|
||||
t, err := A.DelayTest(proxy, url)
|
||||
if err != nil {
|
||||
sigCh <- 0
|
||||
}
|
||||
sigCh <- t
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(time.Millisecond * time.Duration(timeout)):
|
||||
w.WriteHeader(http.StatusRequestTimeout)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Proxy delay test timeout",
|
||||
})
|
||||
case t := <-sigCh:
|
||||
if t == 0 {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "An error occurred in the delay test",
|
||||
})
|
||||
} else {
|
||||
render.JSON(w, r, GetProxyDelayResponse{
|
||||
Delay: t,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
69
hub/route/configs.go
Normal file
69
hub/route/configs.go
Normal file
@ -0,0 +1,69 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
P "github.com/Dreamacro/clash/proxy"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func configRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getConfigs)
|
||||
return r
|
||||
}
|
||||
|
||||
type configSchema struct {
|
||||
Port *int `json:"port"`
|
||||
SocksPort *int `json:"socket-port"`
|
||||
RedirPort *int `json:"redir-port"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
Mode *T.Mode `json:"mode"`
|
||||
LogLevel *log.LogLevel `json:"log-level"`
|
||||
}
|
||||
|
||||
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
general := executor.GetGeneral()
|
||||
render.Respond(w, r, general)
|
||||
}
|
||||
|
||||
func pointerOrDefault (p *int, def int) int {
|
||||
if p != nil {
|
||||
return *p
|
||||
}
|
||||
|
||||
return def
|
||||
}
|
||||
|
||||
func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
general := &configSchema{}
|
||||
if err := render.DecodeJSON(r.Body, general); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.Respond(w, r, ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if general.AllowLan != nil {
|
||||
P.SetAllowLan(*general.AllowLan)
|
||||
}
|
||||
|
||||
ports := P.GetPorts()
|
||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
|
||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
||||
|
||||
if general.Mode != nil {
|
||||
T.Instance().SetMode(*general.Mode)
|
||||
}
|
||||
|
||||
if general.LogLevel != nil {
|
||||
log.SetLevel(*general.LogLevel)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
12
hub/route/ctxkeys.go
Normal file
12
hub/route/ctxkeys.go
Normal file
@ -0,0 +1,12 @@
|
||||
package route
|
||||
|
||||
var (
|
||||
CtxKeyProxyName = contextKey("proxy name")
|
||||
CtxKeyProxy = contextKey("proxy")
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
func (c contextKey) String() string {
|
||||
return "clash context key " + string(c)
|
||||
}
|
22
hub/route/errors.go
Normal file
22
hub/route/errors.go
Normal file
@ -0,0 +1,22 @@
|
||||
package route
|
||||
|
||||
var (
|
||||
ErrUnauthorized = newError("Unauthorized")
|
||||
ErrBadRequest = newError("Body invalid")
|
||||
ErrForbidden = newError("Forbidden")
|
||||
ErrNotFound = newError("Resource not found")
|
||||
ErrRequestTimeout = newError("Timeout")
|
||||
)
|
||||
|
||||
// HTTPError is custom HTTP error for API
|
||||
type HTTPError struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (e *HTTPError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func newError(msg string) *HTTPError {
|
||||
return &HTTPError{Message: msg}
|
||||
}
|
137
hub/route/proxies.go
Normal file
137
hub/route/proxies.go
Normal file
@ -0,0 +1,137 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
A "github.com/Dreamacro/clash/adapters/outbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func proxyRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getProxies)
|
||||
|
||||
r.Route("/{name}", func(r chi.Router) {
|
||||
r.Use(parseProxyName, findProxyByName)
|
||||
r.Get("/", getProxy)
|
||||
r.Get("/delay", getProxyDelay)
|
||||
r.Put("/", updateProxy)
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
// When name is composed of a partial escape string, Golang does not unescape it
|
||||
func parseProxyName(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
if newName, err := url.PathUnescape(name); err == nil {
|
||||
name = newName
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), CtxKeyProxyName, name)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func findProxyByName(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.Context().Value(CtxKeyProxyName).(string)
|
||||
proxies := T.Instance().Proxies()
|
||||
proxy, exist := proxies[name]
|
||||
if !exist {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
render.Respond(w, r, ErrNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func getProxies(w http.ResponseWriter, r *http.Request) {
|
||||
proxies := T.Instance().Proxies()
|
||||
render.Respond(w, r, map[string]map[string]C.Proxy{
|
||||
"proxies": proxies,
|
||||
})
|
||||
}
|
||||
|
||||
func getProxy(w http.ResponseWriter, r *http.Request) {
|
||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||
render.Respond(w, r, proxy)
|
||||
}
|
||||
|
||||
type UpdateProxyRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
req := UpdateProxyRequest{}
|
||||
if err := render.DecodeJSON(r.Body, &req); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.Respond(w, r, ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||
|
||||
selector, ok := proxy.(*A.Selector)
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.Respond(w, r, ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := selector.Set(req.Name); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.Respond(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
url := query.Get("url")
|
||||
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.Respond(w, r, ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||
|
||||
sigCh := make(chan int16)
|
||||
go func() {
|
||||
t, err := A.DelayTest(proxy, url)
|
||||
if err != nil {
|
||||
sigCh <- 0
|
||||
}
|
||||
sigCh <- t
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(time.Millisecond * time.Duration(timeout)):
|
||||
w.WriteHeader(http.StatusRequestTimeout)
|
||||
render.Respond(w, r, ErrRequestTimeout)
|
||||
case t := <-sigCh:
|
||||
if t == 0 {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
render.Respond(w, r, newError("An error occurred in the delay test"))
|
||||
} else {
|
||||
render.Respond(w, r, map[string]int16{
|
||||
"delay": t,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package hub
|
||||
package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
@ -10,7 +12,6 @@ import (
|
||||
func ruleRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getRules)
|
||||
r.Put("/", updateRules)
|
||||
return r
|
||||
}
|
||||
|
||||
@ -20,12 +21,8 @@ type Rule struct {
|
||||
Proxy string `json:"proxy"`
|
||||
}
|
||||
|
||||
type GetRulesResponse struct {
|
||||
Rules []Rule `json:"rules"`
|
||||
}
|
||||
|
||||
func getRules(w http.ResponseWriter, r *http.Request) {
|
||||
rawRules := cfg.Rules()
|
||||
rawRules := T.Instance().Rules()
|
||||
|
||||
var rules []Rule
|
||||
for _, rule := range rawRules {
|
||||
@ -37,19 +34,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
render.JSON(w, r, GetRulesResponse{
|
||||
Rules: rules,
|
||||
render.Respond(w, r, map[string][]Rule{
|
||||
"rules": rules,
|
||||
})
|
||||
}
|
||||
|
||||
func updateRules(w http.ResponseWriter, r *http.Request) {
|
||||
err := cfg.UpdateRules()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
render.JSON(w, r, Error{
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package hub
|
||||
package route
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@ -6,44 +6,32 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/render"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var secret = ""
|
||||
var (
|
||||
serverSecret = ""
|
||||
serverAddr = ""
|
||||
)
|
||||
|
||||
type Traffic struct {
|
||||
Up int64 `json:"up"`
|
||||
Down int64 `json:"down"`
|
||||
}
|
||||
|
||||
func newHub(signal chan struct{}) {
|
||||
var addr string
|
||||
ch := config.Instance().Subscribe()
|
||||
signal <- struct{}{}
|
||||
count := 0
|
||||
for {
|
||||
elm := <-ch
|
||||
event := elm.(*config.Event)
|
||||
switch event.Type {
|
||||
case "external-controller":
|
||||
addr = event.Payload.(string)
|
||||
count++
|
||||
case "secret":
|
||||
secret = event.Payload.(string)
|
||||
count++
|
||||
}
|
||||
if count == 2 {
|
||||
break
|
||||
}
|
||||
func Start(addr string, secret string) {
|
||||
if serverAddr != "" {
|
||||
return
|
||||
}
|
||||
|
||||
serverAddr = addr
|
||||
serverSecret = secret
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
cors := cors.New(cors.Options{
|
||||
@ -59,12 +47,12 @@ func newHub(signal chan struct{}) {
|
||||
r.With(jsonContentType).Get("/logs", getLogs)
|
||||
r.Mount("/configs", configRouter())
|
||||
r.Mount("/proxies", proxyRouter())
|
||||
r.Mount("/rules", ruleRouter())
|
||||
// r.Mount("/rules", ruleRouter())
|
||||
|
||||
log.Infof("RESTful API listening at: %s", addr)
|
||||
log.Infoln("RESTful API listening at: %s", addr)
|
||||
err := http.ListenAndServe(addr, r)
|
||||
if err != nil {
|
||||
log.Errorf("External controller error: %s", err.Error())
|
||||
log.Errorln("External controller error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,18 +69,16 @@ func authentication(next http.Handler) http.Handler {
|
||||
header := r.Header.Get("Authorization")
|
||||
text := strings.SplitN(header, " ", 2)
|
||||
|
||||
if secret == "" {
|
||||
if serverSecret == "" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
hasUnvalidHeader := text[0] != "Bearer"
|
||||
hasUnvalidSecret := len(text) == 2 && text[1] != secret
|
||||
hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret
|
||||
if hasUnvalidHeader || hasUnvalidSecret {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Authentication failed",
|
||||
})
|
||||
render.Respond(w, r, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
@ -100,17 +86,11 @@ func authentication(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
type contextKey string
|
||||
|
||||
func (c contextKey) String() string {
|
||||
return "clash context key " + string(c)
|
||||
}
|
||||
|
||||
func traffic(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
tick := time.NewTicker(time.Second)
|
||||
t := tunnel.Traffic()
|
||||
t := T.Instance().Traffic()
|
||||
for range tick.C {
|
||||
up, down := t.Now()
|
||||
if err := json.NewEncoder(w).Encode(Traffic{
|
||||
@ -134,29 +114,18 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
|
||||
levelText = "info"
|
||||
}
|
||||
|
||||
level, ok := C.LogLevelMapping[levelText]
|
||||
level, ok := log.LogLevelMapping[levelText]
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Level error",
|
||||
})
|
||||
render.Respond(w, r, ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
src := tunnel.Log()
|
||||
sub, err := src.Subscribe()
|
||||
defer src.UnSubscribe(sub)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
render.JSON(w, r, Error{
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
sub := log.Subscribe()
|
||||
render.Status(r, http.StatusOK)
|
||||
for elm := range sub {
|
||||
log := elm.(T.Log)
|
||||
if log.LogLevel > level {
|
||||
log := elm.(*log.Event)
|
||||
if log.LogLevel < level {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -169,10 +138,3 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Run initial hub
|
||||
func Run() {
|
||||
signal := make(chan struct{})
|
||||
go newHub(signal)
|
||||
<-signal
|
||||
}
|
Reference in New Issue
Block a user