beego源碼解析之config模塊

目標

config模塊參考了database/sql中實現模式,接口和實現分離,本教程選取ini格式配置的實現,以及分析beego和config模塊的集成方式,來實現下面三個目標。
  • 理解config包的源碼實現
  • ini格式介紹
  • 理解beego如何使用源碼config

config模塊相關文件

config在beego下config目錄,本文中分析如下兩個文件。css

  • config.go 定義實現的接口與包含接口的使用方式
  • ini.go 爲ini格式的具體實現

image.png
圖1-1mysql

config接口定義

config中定義了兩個接口,Config和Configer。先簡單描述下,詳情見下文,Config負責文件解析並存儲。Configer是對存儲數據的操做。
image.png
圖1-2git

config實現方式

從config.go註釋中,咱們看到config的使用demogithub

// Usage:
//  import "github.com/astaxie/beego/config"
//Examples.
//
//  cnf, err := config.NewConfig("ini", "config.conf")
/

看一下NewConfig函數sql

func NewConfig(adapterName, filename string) (Configer, error) {
    adapter, ok := adapters[adapterName]
    return adapter.Parse(filename)
}

會調用到上圖中Parse函數,因爲咱們做用的ini配置,咱們接下來看ini中的具體實現了。cookie

ini格式介紹(百度百科)

init格式文件實例

appname = beepkg
httpaddr = "127.0.0.1"
; http port
httpport = 9090
[mysql]
mysqluser = "root"
mysqlpass = "rootpass"

節(section)

節用方括號括起來,單獨佔一行,例如:session

[section]app

鍵(key)

鍵(key)又名屬性(property),單獨佔一行用等號鏈接鍵名和鍵值,例如:ide

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": ""},
        },
    }
}
相關文章
相關標籤/搜索