beego源码解析之config模块

prozhou · · 1121 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

目标

config模块参考了database/sql中实现模式,接口和实现分离,本教程选取ini格式配置的实现,以及分析beego和config模块的集成方式,来实现下面三个目标。
  • 理解config包的源码实现
  • ini格式介绍
  • 理解beego如何使用源码config

config模块相关文件

config在beego下config目录,本文中分析以下两个文件。

  • config.go 定义实现的接口与包含接口的使用方式
  • ini.go 为ini格式的具体实现

image.png
图1-1

config接口定义

config中定义了两个接口,Config和Configer。先简单描述下,详情见下文,Config负责文件解析并存储。Configer是对存储数据的操作。
image.png
图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中
image.png
图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": ""},
        },
    }
}

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:prozhou

查看原文:beego源码解析之config模块

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1121 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传