目标
config模块参考了database/sql中实现模式,接口和实现分离,本教程选取ini格式配置的实现,以及分析beego和config模块的集成方式,来实现下面三个目标。
- 理解config包的源码实现
- ini格式介绍
- 理解beego如何使用源码config
config模块相关文件
config在beego下config目录,本文中分析以下两个文件。
- config.go 定义实现的接口与包含接口的使用方式
- ini.go 为ini格式的具体实现
config接口定义
config中定义了两个接口,Config和Configer。先简单描述下,详情见下文,Config负责文件解析并存储。Configer是对存储数据的操作。
图1-2
config实现方式
从config.go注释中,我们看到config的使用demo
// Usage:
// import "github.com/astaxie/beego/config"
//Examples.
//
// cnf, err := config.NewConfig("ini", "config.conf")
/
看一下NewConfig函数
func NewConfig(adapterName, filename string) (Configer, error) {
adapter, ok := adapters[adapterName]
return adapter.Parse(filename)
}
会调用到上图中Parse函数,由于我们作用的ini配置,我们接下来看ini中的具体实现了。
ini格式介绍(百度百科)
init格式文件实例
appname = beepkg
httpaddr = "127.0.0.1"
; http port
httpport = 9090
[mysql]
mysqluser = "root"
mysqlpass = "rootpass"
节(section)
节用方括号括起来,单独占一行,例如:
[section]
键(key)
键(key)又名属性(property),单独占一行用等号连接键名和键值,例如:
name=value
注释(comment)
注释使用英文分号(;)开头,单独占一行。在分号后面的文字,直到该行结尾都全部为注释,例如:
; comment text
ini配置实现
我们看到Parse会最终调用parseData.
func (ini *IniConfig) Parse(name string) (Configer, error) {
return ini.parseFile(name)
}
func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
data, err := ioutil.ReadFile(name)
return ini.parseData(filepath.Dir(name), data)
}
分析下parseData
1,读取section,如果读取不到,作用默认section的name.
2, 读取行,按照=分割为两个值,key=>value
3, 赋值cfg.data[section][key]=value
数据会存储到map中
图1-3
func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) {
cfg := &IniConfigContainer{
...
}
cfg.Lock()
defer cfg.Unlock()
var comment bytes.Buffer
buf := bufio.NewReader(bytes.NewBuffer(data))
section := defaultSection
tmpBuf := bytes.NewBuffer(nil)
for {
tmpBuf.Reset()
shouldBreak := false
//读取一行
....
line := tmpBuf.Bytes()
line = bytes.TrimSpace(line)
//处理注释,忽略
...
//读取section名称
if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) {
section = strings.ToLower(string(line[1 : len(line)-1])) // section name case insensitive
if comment.Len() > 0 {
cfg.sectionComment[section] = comment.String()
comment.Reset()
}
if _, ok := cfg.data[section]; !ok {
cfg.data[section] = make(map[string]string)
}
continue
}
//默认section
if _, ok := cfg.data[section]; !ok {
cfg.data[section] = make(map[string]string)
}
keyValue := bytes.SplitN(line, bEqual, 2)
key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive
key = strings.ToLower(key)
//include 忽略
...
val := bytes.TrimSpace(keyValue[1])
....
cfg.data[section][key] = ExpandValueEnv(string(val))
....
}
return cfg, nil
}
如何读取数据呢,Parse会返回实现Configer的实例,见上图1-2,我们分析下String方法。
func (c *IniConfigContainer) String(key string) string {
return c.getdata(key)
}
看一下getdata方法,如果不指定section,去default取,指定,则从传入的section取。
func (c *IniConfigContainer) getdata(key string) string {
if len(key) == 0 {
return ""
}
c.RLock()
defer c.RUnlock()
var (
section, k string
sectionKey = strings.Split(strings.ToLower(key), "::")
)
if len(sectionKey) >= 2 {
section = sectionKey[0]
k = sectionKey[1]
} else {
section = defaultSection
k = sectionKey[0]
}
if v, ok := c.data[section]; ok {
if vv, ok := v[k]; ok {
return vv
}
}
return ""
}
好了,到现在已经分析完config模块,接下来看下beego如何使用config模块的。
beego中使用config
在beego下的config.go中(不是config/config.go),我们看下init方法
func init() {
//beego默认配置
BConfig = newBConfig()
...
if err = parseConfig(appConfigPath); err != nil {
panic(err)
}
}
看下parseConfig
func parseConfig(appConfigPath string) (err error) {
//前文分析的config模块
AppConfig, err = newAppConfig(appConfigProvider, appConfigPath)
return assignConfig(AppConfig)
}
看一下 assignConfig
func assignConfig(ac config.Configer) error {
for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} {
assignSingleConfig(i, ac)
}
...
}
最后了,看下assignSingleConfig,是使用reflect实现的赋值。这里会优先使用文件中的配置,文件中没才会用默认配置。
func assignSingleConfig(p interface{}, ac config.Configer) {
pt := reflect.TypeOf(p)
...
pt = pt.Elem()
....
pv := reflect.ValueOf(p).Elem()
for i := 0; i < pt.NumField(); i++ {
pf := pv.Field(i)
if !pf.CanSet() {
continue
}
name := pt.Field(i).Name
switch pf.Kind() {
case reflect.String:
pf.SetString(ac.DefaultString(name, pf.String()))
...
}
}
}
看一下beego的默认配置吧
func newBConfig() *Config {
return &Config{
AppName: "beego",
RunMode: PROD,
RouterCaseSensitive: true,
ServerName: "beegoServer:" + VERSION,
RecoverPanic: true,
RecoverFunc: recoverPanic,
CopyRequestBody: false,
EnableGzip: false,
MaxMemory: 1 << 26, //64MB
EnableErrorsShow: true,
EnableErrorsRender: true,
Listen: Listen{
Graceful: false,
ServerTimeOut: 0,
ListenTCP4: false,
EnableHTTP: true,
AutoTLS: false,
Domains: []string{},
TLSCacheDir: ".",
HTTPAddr: "",
HTTPPort: 8080,
EnableHTTPS: false,
HTTPSAddr: "",
HTTPSPort: 10443,
HTTPSCertFile: "",
HTTPSKeyFile: "",
EnableAdmin: false,
AdminAddr: "",
AdminPort: 8088,
EnableFcgi: false,
EnableStdIo: false,
},
WebConfig: WebConfig{
AutoRender: true,
EnableDocs: false,
FlashName: "BEEGO_FLASH",
FlashSeparator: "BEEGOFLASH",
DirectoryIndex: false,
StaticDir: map[string]string{"/static": "static"},
StaticExtensionsToGzip: []string{".css", ".js"},
TemplateLeft: "{{",
TemplateRight: "}}",
ViewsPath: "views",
EnableXSRF: false,
XSRFKey: "beegoxsrf",
XSRFExpire: 0,
Session: SessionConfig{
SessionOn: false,
SessionProvider: "memory",
SessionName: "beegosessionID",
SessionGCMaxLifetime: 3600,
SessionProviderConfig: "",
SessionDisableHTTPOnly: false,
SessionCookieLifeTime: 0, //set cookie default is the browser life
SessionAutoSetCookie: true,
SessionDomain: "",
SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers
SessionNameInHTTPHeader: "Beegosessionid",
SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params
},
},
Log: LogConfig{
AccessLogs: false,
EnableStaticLogs: false,
AccessLogsFormat: "APACHE_FORMAT",
FileLineNum: true,
Outputs: map[string]string{"console": ""},
},
}
}