Support loading config from different formats (#228)

This commit is contained in:
Monsoon 2021-02-12 22:12:58 +08:00 committed by GitHub
parent 96d7156eba
commit 1b87264c53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 112 additions and 30 deletions

View File

@ -22,9 +22,13 @@ type ConfigFormat struct {
// ConfigLoader is a utility to load Xray config from external source. // ConfigLoader is a utility to load Xray config from external source.
type ConfigLoader func(input interface{}) (*Config, error) 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 ( var (
configLoaderByName = make(map[string]*ConfigFormat) configLoaderByName = make(map[string]*ConfigFormat)
configLoaderByExt = make(map[string]*ConfigFormat) configLoaderByExt = make(map[string]*ConfigFormat)
ConfigBuilderForFiles ConfigBuilder
) )
// RegisterConfigLoader add a new ConfigLoader. // RegisterConfigLoader add a new ConfigLoader.
@ -46,6 +50,21 @@ func RegisterConfigLoader(format *ConfigFormat) error {
return nil 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 { func getExtension(filename string) string {
idx := strings.LastIndexByte(filename, '.') idx := strings.LastIndexByte(filename, '.')
if idx == -1 { if idx == -1 {
@ -54,23 +73,48 @@ func getExtension(filename string) string {
return filename[idx+1:] return filename[idx+1:]
} }
// LoadConfig loads config with given format from given source. func getFormat(filename string) string {
// input accepts 2 different types: return GetFormatByExtension(getExtension(filename))
// * []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) { func LoadConfig(formatName string, input interface{}) (*Config, error) {
ext := getExtension(filename) switch v := input.(type) {
if len(ext) > 0 { case cmdarg.Arg:
if f, found := configLoaderByExt[ext]; found {
return f.Loader(input) 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 nil, newError("Unable to load config").AtWarning()
return f.Loader(input)
}
return nil, newError("Unable to load config in ", formatName).AtWarning()
} }
func loadProtobufConfig(data []byte) (*Config, error) { func loadProtobufConfig(data []byte) (*Config, error) {

View File

@ -25,7 +25,7 @@ func CreateObject(v *Instance, config interface{}) (interface{}, error) {
// //
// xray:api:stable // xray:api:stable
func StartInstance(configFormat string, configBytes []byte) (*Instance, error) { 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -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
}

View File

@ -11,7 +11,6 @@ import (
"regexp" "regexp"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"strings"
"syscall" "syscall"
"github.com/xtls/xray-core/common/cmdarg" "github.com/xtls/xray-core/common/cmdarg"
@ -158,30 +157,25 @@ func getConfigFilePath() cmdarg.Arg {
} }
func getConfigFormat() string { func getConfigFormat() string {
switch strings.ToLower(*format) { f := core.GetFormatByExtension(*format)
case "pb", "protobuf": if f == "" {
return "protobuf" f = "json"
case "yaml", "yml":
return "yaml"
case "toml":
return "toml"
default:
return "json"
} }
return f
} }
func startXray() (core.Server, error) { func startXray() (core.Server, error) {
configFiles := getConfigFilePath() 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 { if err != nil {
return nil, newError("failed to load config files: [", configFiles.String(), "]").Base(err) 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 { if err != nil {
return nil, newError("failed to create server").Base(err) return nil, newError("failed to create server").Base(err)
} }