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