diff --git a/config.go b/config.go index 727329fa..bcadbb18 100755 --- a/config.go +++ b/config.go @@ -4,14 +4,13 @@ package core import ( "io" - "io/ioutil" - "os" "strings" "github.com/golang/protobuf/proto" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/cmdarg" + "v2ray.com/core/main/confloader" ) // ConfigFormat is a configurable format of V2Ray config file. @@ -90,18 +89,10 @@ func init() { Loader: func(input interface{}) (*Config, error) { switch v := input.(type) { case cmdarg.Arg: - if len(v) == 0 { - return nil, newError("input has no element") - } - var data []byte - var rerr error - // pb type can only handle the first config - if v[0] == "stdin:" { - data, rerr = buf.ReadAllToBytes(os.Stdin) - } else { - data, rerr = ioutil.ReadFile(v[0]) - } - common.Must(rerr) + r, err := confloader.LoadConfig(v[0]) + common.Must(err) + data, err := buf.ReadAllToBytes(r) + common.Must(err) return loadProtobufConfig(data) case io.Reader: data, err := buf.ReadAllToBytes(v) diff --git a/main/confloader/confloader.go b/main/confloader/confloader.go new file mode 100644 index 00000000..f987ddc7 --- /dev/null +++ b/main/confloader/confloader.go @@ -0,0 +1,30 @@ +package confloader + +import ( + "io" + "os" +) + +type configFileLoader func(string) (io.Reader, error) +type extconfigLoader func([]string) (io.Reader, error) + +var ( + EffectiveConfigFileLoader configFileLoader + EffectiveExtConfigLoader extconfigLoader +) + +func LoadConfig(file string) (io.Reader, error) { + if EffectiveConfigFileLoader == nil { + newError("external config module not loaded, reading from stdin").AtInfo().WriteToLog() + return os.Stdin, nil + } + return EffectiveConfigFileLoader(file) +} + +func LoadExtConfig(files []string) (io.Reader, error) { + if EffectiveExtConfigLoader == nil { + return nil, newError("external config module not loaded").AtError() + } + + return EffectiveExtConfigLoader(files) +} diff --git a/main/confloader/errors.generated.go b/main/confloader/errors.generated.go new file mode 100644 index 00000000..deda6e51 --- /dev/null +++ b/main/confloader/errors.generated.go @@ -0,0 +1,9 @@ +package confloader + +import "v2ray.com/core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/main/confloader/external/errors.generated.go b/main/confloader/external/errors.generated.go new file mode 100644 index 00000000..919f10d0 --- /dev/null +++ b/main/confloader/external/errors.generated.go @@ -0,0 +1,9 @@ +package external + +import "v2ray.com/core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/main/confloader/external/external.go b/main/confloader/external/external.go new file mode 100644 index 00000000..a16f0c7a --- /dev/null +++ b/main/confloader/external/external.go @@ -0,0 +1,86 @@ +package external + +//go:generate errorgen + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "strings" + "time" + + "v2ray.com/core/common/buf" + "v2ray.com/core/common/platform/ctlcmd" + "v2ray.com/core/main/confloader" +) + +func ConfigLoader(arg string) (out io.Reader, err error) { + + var data []byte + if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") { + data, err = FetchHTTPContent(arg) + } else if arg == "stdin:" { + data, err = ioutil.ReadAll(os.Stdin) + } else { + data, err = ioutil.ReadFile(arg) + } + + if err != nil { + return + } + out = bytes.NewBuffer(data) + return +} + +func FetchHTTPContent(target string) ([]byte, error) { + + parsedTarget, err := url.Parse(target) + if err != nil { + return nil, newError("invalid URL: ", target).Base(err) + } + + if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" { + return nil, newError("invalid scheme: ", parsedTarget.Scheme) + } + + client := &http.Client{ + Timeout: 30 * time.Second, + } + resp, err := client.Do(&http.Request{ + Method: "GET", + URL: parsedTarget, + Close: true, + }) + if err != nil { + return nil, newError("failed to dial to ", target).Base(err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return nil, newError("unexpected HTTP status code: ", resp.StatusCode) + } + + content, err := buf.ReadAllToBytes(resp.Body) + if err != nil { + return nil, newError("failed to read HTTP response").Base(err) + } + + return content, nil +} + +func ExtConfigLoader(files []string) (io.Reader, error) { + buf, err := ctlcmd.Run(append([]string{"config"}, files...), os.Stdin) + if err != nil { + return nil, err + } + + return strings.NewReader(buf.String()), nil +} + +func init() { + confloader.EffectiveConfigFileLoader = ConfigLoader + confloader.EffectiveExtConfigLoader = ExtConfigLoader +} diff --git a/main/distro/all/all.go b/main/distro/all/all.go index f7986889..71144c08 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -58,4 +58,7 @@ import ( _ "v2ray.com/core/main/json" // The following line loads JSON internally // _ "v2ray.com/core/main/jsonem" + + // Load config from file or http(s) + _ "v2ray.com/core/main/confloader/external" ) diff --git a/main/json/config_json.go b/main/json/config_json.go index 68334104..59bcc63d 100644 --- a/main/json/config_json.go +++ b/main/json/config_json.go @@ -4,14 +4,12 @@ package json import ( "io" - "os" "v2ray.com/core" "v2ray.com/core/common" - "v2ray.com/core/common/buf" "v2ray.com/core/common/cmdarg" - "v2ray.com/core/common/platform/ctlcmd" "v2ray.com/core/infra/conf/serial" + "v2ray.com/core/main/confloader" ) func init() { @@ -21,13 +19,11 @@ func init() { Loader: func(input interface{}) (*core.Config, error) { switch v := input.(type) { case cmdarg.Arg: - jsonContent, err := ctlcmd.Run(append([]string{"config"}, v...), os.Stdin) + r, err := confloader.LoadExtConfig(v) if err != nil { return nil, newError("failed to execute v2ctl to convert config file.").Base(err).AtWarning() } - return core.LoadConfig("protobuf", "", &buf.MultiBufferContainer{ - MultiBuffer: jsonContent, - }) + return core.LoadConfig("protobuf", "", r) case io.Reader: return serial.LoadJSONConfig(v) default: diff --git a/main/jsonem/jsonem.go b/main/jsonem/jsonem.go index dd447187..6000e285 100644 --- a/main/jsonem/jsonem.go +++ b/main/jsonem/jsonem.go @@ -1,21 +1,14 @@ package jsonem import ( - "bytes" "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "strings" - "time" "v2ray.com/core" "v2ray.com/core/common" - "v2ray.com/core/common/buf" "v2ray.com/core/common/cmdarg" "v2ray.com/core/infra/conf" "v2ray.com/core/infra/conf/serial" + "v2ray.com/core/main/confloader" ) func init() { @@ -28,7 +21,7 @@ func init() { cf := &conf.Config{} for _, arg := range v { newError("Reading config: ", arg).AtInfo().WriteToLog() - r, err := LoadArg(arg) + r, err := confloader.LoadConfig(arg) common.Must(err) c, err := serial.DecodeJSONConfig(r) common.Must(err) @@ -43,57 +36,3 @@ func init() { }, })) } - -func LoadArg(arg string) (out io.Reader, err error) { - - var data []byte - if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") { - data, err = FetchHTTPContent(arg) - } else if arg == "stdin:" { - data, err = ioutil.ReadAll(os.Stdin) - } else { - data, err = ioutil.ReadFile(arg) - } - - if err != nil { - return - } - out = bytes.NewBuffer(data) - return -} - -func FetchHTTPContent(target string) ([]byte, error) { - - parsedTarget, err := url.Parse(target) - if err != nil { - return nil, newError("invalid URL: ", target).Base(err) - } - - if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" { - return nil, newError("invalid scheme: ", parsedTarget.Scheme) - } - - client := &http.Client{ - Timeout: 30 * time.Second, - } - resp, err := client.Do(&http.Request{ - Method: "GET", - URL: parsedTarget, - Close: true, - }) - if err != nil { - return nil, newError("failed to dial to ", target).Base(err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, newError("unexpected HTTP status code: ", resp.StatusCode) - } - - content, err := buf.ReadAllToBytes(resp.Body) - if err != nil { - return nil, newError("failed to read HTTP response").Base(err) - } - - return content, nil -} diff --git a/main/main.go b/main/main.go index 90d6e3de..94ba7088 100644 --- a/main/main.go +++ b/main/main.go @@ -22,7 +22,7 @@ var ( configFiles cmdarg.Arg // "Config file for V2Ray.", the option is customed type, parse in main version = flag.Bool("version", false, "Show current version of V2Ray.") test = flag.Bool("test", false, "Test config file only, without launching V2Ray server.") - format = flag.String("format", "", "Format of input file.") + format = flag.String("format", "json", "Format of input file.") errNoConfig = newError("no valid config") ) @@ -31,23 +31,23 @@ func fileExists(file string) bool { return err == nil && !info.IsDir() } -func getConfigFilePath() cmdarg.Arg { +func getConfigFilePath() (cmdarg.Arg, error) { if len(configFiles) > 0 { - return configFiles + return configFiles, nil } if workingDir, err := os.Getwd(); err == nil { configFile := filepath.Join(workingDir, "config.json") if fileExists(configFile) { - return cmdarg.Arg{configFile} + return cmdarg.Arg{configFile}, nil } } if configFile := platform.GetConfigurationPath(); fileExists(configFile) { - return cmdarg.Arg{configFile} + return cmdarg.Arg{configFile}, nil } - return configFiles + return cmdarg.Arg{"stdin:"}, nil } func GetConfigFormat() string { @@ -60,12 +60,9 @@ func GetConfigFormat() string { } func startV2Ray() (core.Server, error) { - configFiles := getConfigFilePath() - if len(configFiles) == 0 { - if *format == "" { - return nil, errNoConfig - } - configFiles = []string{"stdin:"} + configFiles, err := getConfigFilePath() + if err != nil { + return nil, err } config, err := core.LoadConfig(GetConfigFormat(), configFiles[0], configFiles)