From 1b87264c532b8e250e5098b15fc0b29d7f551e6e Mon Sep 17 00:00:00 2001 From: Monsoon Date: Fri, 12 Feb 2021 22:12:58 +0800 Subject: [PATCH] Support loading config from different formats (#228) --- core/config.go | 76 ++++++++++++++++++++++++++++-------- core/functions.go | 2 +- infra/conf/serial/builder.go | 44 +++++++++++++++++++++ main/run.go | 20 ++++------ 4 files changed, 112 insertions(+), 30 deletions(-) create mode 100644 infra/conf/serial/builder.go diff --git a/core/config.go b/core/config.go index 2aaec619..b5cc8de7 100644 --- a/core/config.go +++ b/core/config.go @@ -22,9 +22,13 @@ type ConfigFormat struct { // ConfigLoader is a utility to load Xray config from external source. type ConfigLoader func(input interface{}) (*Config, error) +// ConfigBuilder is a builder to build core.Config from filenames and formats +type ConfigBuilder func(files []string, formats []string) (*Config, error) + var ( - configLoaderByName = make(map[string]*ConfigFormat) - configLoaderByExt = make(map[string]*ConfigFormat) + configLoaderByName = make(map[string]*ConfigFormat) + configLoaderByExt = make(map[string]*ConfigFormat) + ConfigBuilderForFiles ConfigBuilder ) // RegisterConfigLoader add a new ConfigLoader. @@ -46,6 +50,21 @@ func RegisterConfigLoader(format *ConfigFormat) error { return nil } +func GetFormatByExtension(ext string) string { + switch strings.ToLower(ext) { + case "pb", "protobuf": + return "protobuf" + case "yaml", "yml": + return "yaml" + case "toml": + return "toml" + case "json": + return "json" + default: + return "" + } +} + func getExtension(filename string) string { idx := strings.LastIndexByte(filename, '.') if idx == -1 { @@ -54,23 +73,48 @@ func getExtension(filename string) string { return filename[idx+1:] } -// LoadConfig loads config with given format from given source. -// input accepts 2 different types: -// * []string slice of multiple filename/url(s) to open to read -// * io.Reader that reads a config content (the original way) -func LoadConfig(formatName string, filename string, input interface{}) (*Config, error) { - ext := getExtension(filename) - if len(ext) > 0 { - if f, found := configLoaderByExt[ext]; found { - return f.Loader(input) +func getFormat(filename string) string { + return GetFormatByExtension(getExtension(filename)) +} + +func LoadConfig(formatName string, input interface{}) (*Config, error) { + switch v := input.(type) { + case cmdarg.Arg: + + formats := make([]string, len(v)) + hasProtobuf := false + for i, file := range v { + f := getFormat(file) + if f == "" { + f = formatName + } + if f == "protobuf" { + hasProtobuf = true + } + formats[i] = f + } + + // only one protobuf config file is allowed + if hasProtobuf { + if len(v) == 1 { + return configLoaderByName["protobuf"].Loader(v) + } else { + return nil, newError("Only one protobuf config file is allowed").AtWarning() + } + } + + // to avoid import cycle + return ConfigBuilderForFiles(v, formats) + + case io.Reader: + if f, found := configLoaderByName[formatName]; found { + return f.Loader(v) + } else { + return nil, newError("Unable to load config in", formatName).AtWarning() } } - if f, found := configLoaderByName[formatName]; found { - return f.Loader(input) - } - - return nil, newError("Unable to load config in ", formatName).AtWarning() + return nil, newError("Unable to load config").AtWarning() } func loadProtobufConfig(data []byte) (*Config, error) { diff --git a/core/functions.go b/core/functions.go index 1de87e48..11282462 100644 --- a/core/functions.go +++ b/core/functions.go @@ -25,7 +25,7 @@ func CreateObject(v *Instance, config interface{}) (interface{}, error) { // // xray:api:stable func StartInstance(configFormat string, configBytes []byte) (*Instance, error) { - config, err := LoadConfig(configFormat, "", bytes.NewReader(configBytes)) + config, err := LoadConfig(configFormat, bytes.NewReader(configBytes)) if err != nil { return nil, err } diff --git a/infra/conf/serial/builder.go b/infra/conf/serial/builder.go new file mode 100644 index 00000000..651f24c9 --- /dev/null +++ b/infra/conf/serial/builder.go @@ -0,0 +1,44 @@ +package serial + +import ( + "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/infra/conf" + "github.com/xtls/xray-core/main/confloader" + "io" +) + +func BuildConfig(files []string, formats []string) (*core.Config, error) { + + cf := &conf.Config{} + for i, file := range files { + newError("Reading config: ", file).AtInfo().WriteToLog() + r, err := confloader.LoadConfig(file) + if err != nil { + return nil, newError("failed to read config: ", file).Base(err) + } + c, err := ReaderDecoderByFormat[formats[i]](r) + if err != nil { + return nil, newError("failed to decode config: ", file).Base(err) + } + if i == 0 { + *cf = *c + continue + } + cf.Override(c, file) + } + return cf.Build() +} + +type readerDecoder func(io.Reader) (*conf.Config, error) + +var ( + ReaderDecoderByFormat = make(map[string]readerDecoder) +) + +func init() { + ReaderDecoderByFormat["json"] = DecodeJSONConfig + ReaderDecoderByFormat["yaml"] = DecodeYAMLConfig + ReaderDecoderByFormat["toml"] = DecodeTOMLConfig + + core.ConfigBuilderForFiles = BuildConfig +} diff --git a/main/run.go b/main/run.go index 9676952e..e0485882 100644 --- a/main/run.go +++ b/main/run.go @@ -11,7 +11,6 @@ import ( "regexp" "runtime" "runtime/debug" - "strings" "syscall" "github.com/xtls/xray-core/common/cmdarg" @@ -158,30 +157,25 @@ func getConfigFilePath() cmdarg.Arg { } func getConfigFormat() string { - switch strings.ToLower(*format) { - case "pb", "protobuf": - return "protobuf" - case "yaml", "yml": - return "yaml" - case "toml": - return "toml" - default: - return "json" + f := core.GetFormatByExtension(*format) + if f == "" { + f = "json" } + return f } func startXray() (core.Server, error) { configFiles := getConfigFilePath() - config, err := core.LoadConfig(getConfigFormat(), configFiles[0], configFiles) + //config, err := core.LoadConfig(getConfigFormat(), configFiles[0], configFiles) - //config, err := core.LoadConfigs(getConfigFormat(), configFiles) + c, err := core.LoadConfig(getConfigFormat(), configFiles) if err != nil { return nil, newError("failed to load config files: [", configFiles.String(), "]").Base(err) } - server, err := core.New(config) + server, err := core.New(c) if err != nil { return nil, newError("failed to create server").Base(err) }