11

beego源码解析之config模块

 4 years ago
source link: https://segmentfault.com/a/1190000021283533
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

目标

config模块参考了database/sql中实现模式,接口和实现分离,本教程选取ini格式配置的实现,以及分析beego和config模块的集成方式,来实现下面三个目标。

  • 理解config包的源码实现
  • ini格式介绍
  • 理解beego如何使用源码config

config模块相关文件

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

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

7ZJVV3e.png!web

图1-1

config接口定义

config中定义了两个接口,Config和Configer。先简单描述下,详情见下文,Config负责文件解析并存储。Configer是对存储数据的操作。

aqaymiI.png!web

图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中

rYfMVru.png!web

图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": ""},
        },
    }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK