The config module of beego source parsing

Keywords: Go Session Database SQL github

target

The config module refers to the implementation mode, interface and implementation separation in database/sql. In this tutorial, the implementation of ini format configuration is selected and the integration mode of beego and config module is analyzed to achieve the following three goals.

  • Understand the source code implementation of config package
  • Introduction to ini format
  • Understand how beego uses source config

config module related files

Config is in the config directory under beego. The following two files are analyzed in this article.

  • config.go defines the interface implemented and how to use the included interface
  • Implementation of ini.go as inI format


Figure 1-1

config interface definition

Config defines two interfaces, config and Configer. For details, see below. Config is responsible for file parsing and storage. Configger is an operation to store data.

Figure 1-2

config implementation

From the config.go annotation, we can see the demo of using config

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

Take a look at the NewConfig function

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

We will call the Parse function in the figure above. Because of the INI configuration we used, we will see the specific implementation in ini.

Introduction to ini format (Baidu Encyclopedia)

init format file instance

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

Section (section)

A section is enclosed in square brackets and occupies a single line, for example:

[section]

Key (key)

Key, also known as property, takes a single line and connects the key name and value with an equal sign, for example:

name=value

Comment

Comments start with an English semicolon (;), which takes up a single line. The text after the semicolon is completely annotated until the end of the line, for example:

; comment text

ini configuration implementation

We see that Parse will eventually call 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 under analysis
1. Read the section. If the section cannot be read, the default section name is used
2. Read the line and divide it into two values according to = key = > value
3. Assign cfg.data[section][key]=value
The data is stored in the map

Figure 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
        //Read a row
        ....
        line := tmpBuf.Bytes()
        line = bytes.TrimSpace(line)
        //Processing comments, ignoring
        ...

        //Read section name
        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
        }
        
        //Default 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 ignore
        ...
        val := bytes.TrimSpace(keyValue[1])
        ....
        cfg.data[section][key] = ExpandValueEnv(string(val)) 
        ....

    }
    return cfg, nil
}

How to read the data? Parse will return the instance that implements Configer, as shown in Figure 1-2 above. We will analyze the String method.

func (c *IniConfigContainer) String(key string) string {
    return c.getdata(key)
}

Take a look at the getdata method. If you don't specify a section, go to default and specify it, you will get it from the incoming 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 ""
}

Now that we have analyzed the config module, let's see how beego uses the config module.

Using config in beego

In config.go under beego (not config/config.go), let's look at the init method

func init() {
    //beego default configuration
    BConfig = newBConfig()
    ...
    if err = parseConfig(appConfigPath); err != nil {
        panic(err)
    }
}

Take a look at parseConfig

func parseConfig(appConfigPath string) (err error) {
    //The config module analyzed above
    AppConfig, err = newAppConfig(appConfigProvider, appConfigPath)
    
    return assignConfig(AppConfig)
}

Take a look at assignConfig

func assignConfig(ac config.Configer) error {
    for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} {
        assignSingleConfig(i, ac)
    }
    ...
}

Finally, let's look at assignSingleConfig, which is an assignment implemented with reflect. Here, the configuration in the file will be used first. If there is no default configuration in the file, the default configuration will be used.

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()))
      ...
        }
    }

}

Take a look at the default configuration of 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": ""},
        },
    }
}

Posted by rogair on Fri, 13 Dec 2019 04:10:29 -0800