From 3e4bc9f85cb2191ebe6994f74138ddf2e12f1758 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 1 Dec 2019 13:22:47 +0800 Subject: [PATCH] Feature: update config API support raw yaml payload --- config/config.go | 54 ++++++---------------------------------- constant/path.go | 10 ++++++++ hub/executor/executor.go | 52 +++++++++++++++++++++++++++++++++++++- hub/route/configs.go | 38 +++++++++++++++++++--------- 4 files changed, 94 insertions(+), 60 deletions(-) diff --git a/config/config.go b/config/config.go index 1e5ab5fc..e5358056 100644 --- a/config/config.go +++ b/config/config.go @@ -2,11 +2,9 @@ package config import ( "fmt" - "io/ioutil" "net" "net/url" "os" - "path/filepath" "strings" adapters "github.com/Dreamacro/clash/adapters/outbound" @@ -109,40 +107,12 @@ type rawConfig struct { Rule []string `yaml:"Rule"` } -// forward compatibility before 1.0 -func readRawConfig(path string) ([]byte, error) { - data, err := ioutil.ReadFile(path) - if err == nil && len(data) != 0 { - return data, nil - } - - if filepath.Ext(path) != ".yaml" { - return nil, err - } - - path = path[:len(path)-5] + ".yml" - if _, fallbackErr := os.Stat(path); fallbackErr == nil { - return ioutil.ReadFile(path) - } - - return data, err -} - -func readConfig(path string) (*rawConfig, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - return nil, err - } - data, err := readRawConfig(path) - if err != nil { - return nil, err - } - - if len(data) == 0 { - return nil, fmt.Errorf("Configuration file %s is empty", path) - } +// Parse config +func Parse(buf []byte) (*Config, error) { + config := &Config{} // config with some default value - rawConfig := &rawConfig{ + rawCfg := &rawConfig{ AllowLan: false, BindAddress: "*", Mode: T.Rule, @@ -164,18 +134,10 @@ func readConfig(path string) (*rawConfig, error) { }, }, } - err = yaml.Unmarshal([]byte(data), &rawConfig) - return rawConfig, err -} - -// Parse config -func Parse(path string) (*Config, error) { - config := &Config{} - - rawCfg, err := readConfig(path) - if err != nil { + if err := yaml.Unmarshal(buf, &rawCfg); err != nil { return nil, err } + config.Experimental = &rawCfg.Experimental general, err := parseGeneral(rawCfg) @@ -226,9 +188,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) { logLevel := cfg.LogLevel if externalUI != "" { - if !filepath.IsAbs(externalUI) { - externalUI = filepath.Join(C.Path.HomeDir(), externalUI) - } + externalUI = C.Path.Reslove(externalUI) if _, err := os.Stat(externalUI); os.IsNotExist(err) { return nil, fmt.Errorf("external-ui: %s not exist", externalUI) diff --git a/constant/path.go b/constant/path.go index 087a6777..9af355b2 100644 --- a/constant/path.go +++ b/constant/path.go @@ -3,6 +3,7 @@ package constant import ( "os" P "path" + "path/filepath" ) const Name = "clash" @@ -43,6 +44,15 @@ func (p *path) Config() string { return p.configFile } +// Reslove return a absolute path or a relative path with homedir +func (p *path) Reslove(path string) string { + if !filepath.IsAbs(path) { + return filepath.Join(p.HomeDir(), path) + } + + return path +} + func (p *path) MMDB() string { return P.Join(p.homeDir, "Country.mmdb") } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 321d6830..ddfa3e6d 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -1,6 +1,11 @@ package executor import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "github.com/Dreamacro/clash/component/auth" trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/config" @@ -12,6 +17,41 @@ import ( T "github.com/Dreamacro/clash/tunnel" ) +// forward compatibility before 1.0 +func readRawConfig(path string) ([]byte, error) { + data, err := ioutil.ReadFile(path) + if err == nil && len(data) != 0 { + return data, nil + } + + if filepath.Ext(path) != ".yaml" { + return nil, err + } + + path = path[:len(path)-5] + ".yml" + if _, fallbackErr := os.Stat(path); fallbackErr == nil { + return ioutil.ReadFile(path) + } + + return data, err +} + +func readConfig(path string) ([]byte, error) { + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil, err + } + data, err := readRawConfig(path) + if err != nil { + return nil, err + } + + if len(data) == 0 { + return nil, fmt.Errorf("Configuration file %s is empty", path) + } + + return data, err +} + // Parse config with default config path func Parse() (*config.Config, error) { return ParseWithPath(C.Path.Config()) @@ -19,7 +59,17 @@ func Parse() (*config.Config, error) { // ParseWithPath parse config with custom config path func ParseWithPath(path string) (*config.Config, error) { - return config.Parse(path) + buf, err := readConfig(path) + if err != nil { + return nil, err + } + + return config.Parse(buf) +} + +// Parse config with default config path +func ParseWithBytes(buf []byte) (*config.Config, error) { + return ParseWithPath(C.Path.Config()) } // ApplyConfig dispatch configure to all parts diff --git a/hub/route/configs.go b/hub/route/configs.go index 08856a43..ea720a7f 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -4,6 +4,7 @@ import ( "net/http" "path/filepath" + "github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/log" P "github.com/Dreamacro/clash/proxy" @@ -77,7 +78,8 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { } type updateConfigRequest struct { - Path string `json:"path"` + Path string `json:"path"` + Payload string `json:"payload"` } func updateConfigs(w http.ResponseWriter, r *http.Request) { @@ -88,18 +90,30 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { return } - if !filepath.IsAbs(req.Path) { - render.Status(r, http.StatusBadRequest) - render.JSON(w, r, newError("path is not a absoluted path")) - return - } - force := r.URL.Query().Get("force") == "true" - cfg, err := executor.ParseWithPath(req.Path) - if err != nil { - render.Status(r, http.StatusBadRequest) - render.JSON(w, r, newError(err.Error())) - return + var cfg *config.Config + var err error + + if req.Payload != "" { + cfg, err = executor.ParseWithBytes([]byte(req.Payload)) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError(err.Error())) + return + } + } else { + if !filepath.IsAbs(req.Path) { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError("path is not a absoluted path")) + return + } + + cfg, err = executor.ParseWithPath(req.Path) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError(err.Error())) + return + } } executor.ApplyConfig(cfg, force)