配置模块是基础模块之一,对不同类型的配置文件提供了一种抽象。
beego目前支持INI
、XML
、JSON
、YAML
格式的配置文件解析,但是默认采用了INI
格式解析,用户可以通过简
单的配置就可以获得很大的灵活性。同时也支持以 etcd
作为远程配置中心。
BConfig
是默认的 Web 配置实例。默认情况下,Beego 会解析当前应用下的 conf/app.conf
文件,用于初始
化 web.BConfig
。
通过这个文件你可以初始化很多 beego 的默认参数:
appname = beepkg
httpaddr = "127.0.0.1"
httpport = 9090
runmode ="dev"
autorender = false
recoverpanic = false
viewspath = "myview"
这里需要注意的是,配置项并没有使用驼峰命名,而是保持了全小写。但是不同的配置格式,也可以支持驼峰命名
和下划线命名。例如 JSON 的配置格式,则只跟你结构体里面 json 标签的取值有关。
它们都维护在结构体 beego/server/web#Config
。
上面这些参数会替换 beego 默认的一些参数,beego 的参数主要有哪些,请参考:
https://godoc.org/github.com/beego/beego#pkg-constants
https://pkg.go.dev/github.com/beego/beego?utm_source=godoc#pkg-types
BConfig
就是beego里面的默认的配置,也是结构体 beego/server/web#Config
的实例。
你也可以直接通过web.BConfig.AppName="beepkg"
这样来修改,和上面的配置效果一样,只是一个在代码里面
写死了,而配置文件就会显得更加灵活。
你也可以在配置文件中配置应用需要用的一些配置信息,例如下面所示的数据库信息:
mysqluser = "root"
mysqlpass = "rootpass"
mysqlurls = "127.0.0.1"
mysqldb = "beego"
那么你就可以通过如下的方式获取设置的配置信息:
beego.AppConfig.String("mysqluser")
beego.AppConfig.String("mysqlpass")
beego.AppConfig.String("mysqlurls")
beego.AppConfig.String("mysqldb")
各种Config
拥有的方法都是一样的,具体可以参考Config API
:
https://github.com/beego/beego/blob/develop/core/config/config.go
Config
的方法如下:
- Set(key, val string) error
- String(key string) (string, error)
- Strings(key string) ([]string, error)
- Int(key string) (int, error)
- Int64(key string) (int64, error)
- Bool(key string) (bool, error)
- Float(key string) (float64, error)
- DefaultString(key string, defaultVal string) string
- DefaultStrings(key string, defaultVal []string) []string
- DefaultInt(key string, defaultVal int) int
- DefaultInt64(key string, defaultVal int64) int64
- DefaultBool(key string, defaultVal bool) bool
- DefaultFloat(key string, defaultVal float64) float64
- DIY(key string) (interface{}, error)
- GetSection(section string) (map[string]string, error)
- Unmarshaler(prefix string, obj interface{}, opt …DecodeOption) error
- Sub(key string) (Configer, error)
- OnChange(key string, fn func(value string))
- SaveConfigFile(filename string) error
Config
的使用方法:
package main
import (
"github.com/beego/beego/v2/core/config"
"log"
)
func main() {
cnf, err := config.NewConfig("ini", "../conf/app.conf")
if err != nil {
log.Println(err)
} else {
port, _ := cnf.String("httpport")
// 2023/02/19 17:46:25 9090
log.Println(port)
}
}
package main
import (
"github.com/beego/beego/v2/server/web"
"log"
)
const ConfigFile = "./conf/app.conf"
func main() {
err := web.LoadAppConfig("ini", ConfigFile)
if err != nil {
log.Println("An error occurred:", err)
}
val, _ := web.AppConfig.Int("httpport")
// 2023/02/21 09:34:17 load config name is 9090
log.Println("load config name is", val)
}
可以重复调用多次LoadAppConfig
,如果后面的文件和前面的 key 冲突,那么以最新加载的为最新值。
在使用ini类型的配置文件中, key 支持section::key
模式。
你可以用Default*
方法返回默认值。
Web 模块封装了配置模块,可以参考Web 配置。
这里有一些使用的注意事项:
1、所有的Default*
方法,在key
不存在,或者查找的过程中,出现error
,都会返回默认值;
2、DIY
直接返回对应的值,而没有做任何类型的转换。当你使用这个方法的时候,你应该自己确认值的类型。只
有在极少数的情况下你才应该考虑使用这个方法;
3、GetSection
会返回section
所对应的部分配置。section
如何被解释,取决于具体的实现;
4、Unmarshaler
会尝试用当且配置的值来初始化obj
。需要注意的是,prefix
的概念类似于section
;
5、Sub
类似与GetSection
,都是尝试返回配置的一部分。所不同的是,GetSection
将结果组织成map
,而
Sub
将结果组织成Config
实例;
6、OnChange
主要用于监听配置的变化。对于大部分依赖于文件系统的实现来说,都不支持。具体而言,我们设
计这个主要是为了考虑支持远程配置;
7、SaveConfigFile
尝试将配置导出成为一个文件;
8、某些实现支持分段式的key
。比如说a.b.c
这种,但是,并不是所有的实现都支持,也不是所有的实现都采
用.
作为分隔符。这是一个历史遗留问题,为了保留兼容性,我们无法在这方面保持一致。
也可以手动初始化全局实例,以指定不同的配置类型,例如说启用etcd
:
config.InitGlobalInstance("etcd", "etcd address")
在配置文件里面支持 section,可以有不同的runmode的配置,默认优先读取runmode下的配置信息,例如下面
的配置文件:
appname = beepkg
httpaddr = "127.0.0.1"
httpport = 9090
runmode ="dev"
autorender = false
recoverpanic = false
viewspath = "myview"
[dev]
httpport = 8080
[prod]
httpport = 8088
[test]
httpport = 8888
上面的配置文件就是在不同的 runmode 下解析不同的配置,例如在 dev 模式下,httpport 是 8080,在 prod 模
式下是 8088,在 test 模式下是 8888。其他配置文件同理。解析的时候优先解析 runmode 下的配置,然后解析
默认的配置。
读取不同模式下配置参数的方法是模式::配置参数名
,比如:beego.AppConfig.String("dev::mysqluser")
。
package main
import (
"github.com/beego/beego/v2/core/config"
"log"
)
func main() {
cnf, err := config.NewConfig("ini", "../conf/app1.conf")
if err != nil {
log.Println(err)
} else {
// 2023/02/19 18:33:52 8088 <nil>
log.Println(cnf.String("prod::httpport"))
}
}
对于自定义的参数,需使用GetConfig(typ, key string, defaultVal interface{})
来获取指定 runmode
下的配置(需1.4.0 以上版本),typ 为参数类型,key 为参数名,defaultVal 为默认值。
package main
import (
beego "github.com/beego/beego/v2/server/web"
"log"
)
func main() {
// beego.GetConfig可以获取系统配置和自定义配置
// beego.AppConfig.String可以获取系统配置和自定义配置
// 但是beego.AppConfig.String在项目启动的时候才可以读取自定义配置
// 2023/02/19 22:14:36 myproject <nil>
log.Println(beego.GetConfig("String", "appname", "myapp"))
// 2023/02/19 22:14:36 9090 <nil>
log.Println(beego.GetConfig("Int", "httpport", "8080"))
// 2023/02/19 22:15:45 root:root@tcp(127.0.0.1:3306)/test <nil>
log.Println(beego.GetConfig("String", "mysqlurl", ""))
// 2023/02/19 22:24:13 <nil>
log.Println(beego.AppConfig.String("mysqluser"))
// 2023/02/19 22:24:13 9090 <nil>
log.Println(beego.AppConfig.String("httpport"))
beego.AppConfig.Set("mode", "dev")
// 2023/02/19 22:31:09 dev <nil>
log.Println(beego.AppConfig.String("mode"))
// 2023/02/19 22:31:09 dev <nil>
log.Println(beego.GetConfig("String", "mode", "test"))
}
INI 格式配置支持 include
方式,引用多个配置文件,例如下面的两个配置文件效果同上:
app.conf:
appname = beepkg
httpaddr = "127.0.0.1"
httpport = 9090
include "app2.conf"
app2.conf:
runmode ="dev"
autorender = false
recoverpanic = false
viewspath = "myview"
[dev]
httpport = 8080
[prod]
httpport = 8088
[test]
httpport = 8888
配置文件解析支持从环境变量中获取配置项,配置项格式:${环境变量}
。例如下面的配置中优先使用环境变量中
配置的 runmode 和 httpport,如果有配置环境变量 ProRunMode 则优先使用该环境变量值。如果不存在或者为
空,则使用 dev 作为 runmode。app.conf:
runmode = "${ProRunMode||dev}"
httpport = "${ProPort||9090}"
beego 中带有很多可配置的参数,我们来一一认识一下它们,这样有利于我们在接下来的 beego 开发中可以充分
的发挥他们的作用(你可以通过在 conf/app.conf
中设置对应的值,不区分大小写)。
详细的字段含义可以直接参考源码Config 定义:
https://github.com/beego/beego/blob/develop/server/web/config.go
BConfig 保存了所有 beego 里面的系统默认参数,你可以通过 web.BConfig
来访问和修改底下的所有配置
信息。
配置文件路径,默认是应用程序对应的目录下的
conf/app.conf
,用户可以在程序代码中加载自己的配置文件
beego.LoadAppConfig("ini", "conf/app2.conf")
也可以加载多个文件,只要你调用多次就可以了,如果后面的文件和前面的 key 冲突,那么以最新加载的为最新值。
AppName
应用名称,默认是 beego。通过 bee new
创建的是创建的项目名。
web.BConfig.AppName = "beego"
RunMode
应用的运行模式,可选值为 prod
, dev
或者 test
。 默认是 dev
,为开发模式,在开发模式下出错会提示
友好的出错页面,如前面错误描述中所述。
web.BConfig.RunMode = "dev"
RouterCaseSensitive
是否路由忽略大小写匹配,默认是 true,区分大小写。
web.BConfig.RouterCaseSensitive = true
ServerName
beego 服务器默认在请求的时候输出 server 为 beego。
web.BConfig.ServerName = "beego"
RecoverPanic
是否异常恢复,默认值为 true,即当应用出现异常的情况,通过 recover 恢复回来,而不会导致应用异常退
出。
web.BConfig.RecoverPanic = true
CopyRequestBody
是否允许在 HTTP 请求时,返回原始请求体数据字节,默认为 false (GET or HEAD or 上传文件请求除外)。
web.BConfig.CopyRequestBody = false
EnableGzip
是否开启 gzip 支持,默认为 false 不支持 gzip,一旦开启了 gzip,那么在模板输出的内容会进行 gzip 或者
zlib 压缩,根据用户的 Accept-Encoding 来判断。
web.BConfig.EnableGzip = false
Gzip允许用户自定义压缩级别、压缩长度阈值和针对请求类型压缩:
1、压缩级别,gzipCompressLevel = 9
,取值为 1~9,如果不设置为 1(最快压缩)
2、压缩长度阈值,gzipMinLength = 256
,当原始内容长度大于此阈值时才开启压缩,默认为 20B(ngnix
默认长度)
3、请求类型,includedMethods = get;post
,针对哪些请求类型进行压缩,默认只针对 GET 请求压缩
MaxMemory
文件上传默认内存缓存大小,默认值是 1 << 26
(64M)。
web.BConfig.MaxMemory = 1 << 26
EnableErrorsShow
是否显示系统错误信息,默认为 true。
web.BConfig.EnableErrorsShow = true
EnableErrorsRender
是否将错误信息进行渲染,默认值为 true,即出错会提示友好的出错页面,对于 API 类型的应用可能需要将
该选项设置为 false 以阻止在 dev
模式下不必要的模板渲染信息返回。
AutoRender
是否模板自动渲染,默认值为 true,对于 API 类型的应用,应用需要把该选项设置为 false,不需要渲染模
板。
web.BConfig.WebConfig.AutoRender = true
EnableDocs
是否开启文档内置功能,默认是 false
web.BConfig.WebConfig.EnableDocs = true
FlashName
Flash 数据设置时 Cookie 的名称,默认是 BEEGO_FLASH
web.BConfig.WebConfig.FlashName = "BEEGO_FLASH"
FlashSeperator
Flash 数据的分隔符,默认是 BEEGOFLASH
web.BConfig.WebConfig.FlashSeparator = "BEEGOFLASH"
DirectoryIndex
是否开启静态目录的列表显示,默认不显示目录,返回 403 错误。
web.BConfig.WebConfig.DirectoryIndex = false
StaticDir
静态文件目录设置,默认是static
可配置单个或多个目录:
1、单个目录, StaticDir = download
,相当于 beego.SetStaticPath("/download","download")
2、多个目录, StaticDir = download:down download2:down2
,相当于
beego.SetStaticPath("/download","down")
和 beego.SetStaticPath("/download2","down2")
web.BConfig.WebConfig.StaticDir
StaticExtensionsToGzip
允许哪些后缀名的静态文件进行 gzip 压缩,默认支持 .css 和 .js
web.BConfig.WebConfig.StaticExtensionsToGzip = []string{".css", ".js"}
等价 config 文件中
StaticExtensionsToGzip = .css, .js
TemplateLeft
模板左标签,默认值是{{
。
web.BConfig.WebConfig.TemplateLeft="{{"
TemplateRight
模板右标签,默认值是}}
。
web.BConfig.WebConfig.TemplateRight="}}"
ViewsPath
模板路径,默认值是 views。
web.BConfig.WebConfig.ViewsPath="views"
EnableXSRF
是否开启 XSRF,默认为 false,不开启。
web.BConfig.WebConfig.EnableXSRF = false
XSRFKEY
XSRF 的 key 信息,默认值是 beegoxsrf。 EnableXSRF=true 才有效
web.BConfig.WebConfig.XSRFKEY = "beegoxsrf"
XSRFExpire
XSRF 过期时间,默认值是 0,不过期。
web.BConfig.WebConfig.XSRFExpire = 0
CommentRouterPath
CommentRouterPath 注解路由所在位置。默认值是controllers
。 Beego 会在启动的时候扫描下面的文件
生成了路由。 web.BConfig.WebConfig.CommentRouterPath = "controllers"
Graceful
是否开启热升级,默认是 false,关闭热升级。
web.BConfig.Listen.Graceful=false
ServerTimeOut
设置 HTTP 的超时时间,默认是 0,不超时。
web.BConfig.Listen.ServerTimeOut=0
ListenTCP4
监听本地网络地址类型,默认是TCP6,可以通过设置为true设置为TCP4。
web.BConfig.Listen.ListenTCP4 = true
EnableHTTP
是否启用 HTTP 监听,默认是 true。
web.BConfig.Listen.EnableHTTP = true
HTTPAddr
应用监听地址,默认为空,监听所有的网卡 IP。
web.BConfig.Listen.HTTPAddr = ""
HTTPPort
应用监听端口,默认为 8080。
web.BConfig.Listen.HTTPPort = 8080
EnableHTTPS
是否启用 HTTPS,默认是 false 关闭。当需要启用时,先设置 EnableHTTPS = true,并设置 HTTPSCertFile
和 HTTPSKeyFile
web.BConfig.Listen.EnableHTTPS = false
HTTPSAddr
应用监听地址,默认为空,监听所有的网卡 IP。
web.BConfig.Listen.HTTPSAddr = ""
HTTPSPort
应用监听端口,默认为 10443
web.BConfig.Listen.HTTPSPort = 10443
HTTPSCertFile
开启 HTTPS 后,ssl 证书路径,默认为空。
web.BConfig.Listen.HTTPSCertFile = "conf/ssl.crt"
HTTPSKeyFile
开启 HTTPS 之后,SSL 证书 keyfile 的路径。
web.BConfig.Listen.HTTPSKeyFile = "conf/ssl.key"
EnableAdmin
是否开启进程内监控模块,默认 false 关闭。
web.BConfig.Listen.EnableAdmin = false
AdminAddr
监控程序监听的地址,默认值是 localhost 。
web.BConfig.Listen.AdminAddr = "localhost"
AdminPort
监控程序监听的地址,默认值是 8088 。
web.BConfig.Listen.AdminPort = 8088
EnableFcgi
是否启用 fastcgi, 默认是 false。
web.BConfig.Listen.EnableFcgi = false
EnableStdIo
通过fastcgi 标准I/O,启用 fastcgi 后才生效,默认 false。
web.BConfig.Listen.EnableStdIo = false
SessionOn
session 是否开启,默认是 false。
web.BConfig.WebConfig.Session.SessionOn = false
SessionProvider
session 的引擎,默认是 memory,详细参见 session 模块
。
web.BConfig.WebConfig.Session.SessionProvider = ""
SessionName
存在客户端的 cookie 名称,默认值是 beegosessionID。
web.BConfig.WebConfig.Session.SessionName = "beegosessionID"
SessionGCMaxLifetime
session 过期时间,默认值是 3600 秒。
web.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600
SessionProviderConfig
配置信息,根据不同的引擎设置不同的配置信息,详细的配置请看下面的引擎设置,详细参见session模块。
SessionCookieLifeTime
session 默认存在客户端的 cookie 的时间,默认值是 3600 秒。
web.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600
SessionAutoSetCookie
是否开启SetCookie,默认值 true 开启。
web.BConfig.WebConfig.Session.SessionAutoSetCookie = true
SessionDomain
session cookie 存储域名,默认空。
web.BConfig.WebConfig.Session.SessionDomain = ""
AccessLogs
是否输出日志到 Log,默认在 prod 模式下不会输出日志,默认为 false 不输出日志。此参数不支持配置文件
配置。
web.BConfig.Log.AccessLogs = false
FileLineNum
是否在日志里面显示文件名和输出日志行号,默认 true。此参数不支持配置文件配置。
web.BConfig.Log.FileLineNum = true
Outputs
日志输出配置,参考 logs 模块,console file 等配置,此参数不支持配置文件配置。
web.BConfig.Log.Outputs = map[string]string{"console": ""}
or
web.BConfig.Log.Outputs["console"] = ""
注意,所以的相对文件路径,都是从你的工作目录开始计算! 其次,除了默认的 INI
格式,其余格式都需要采用
匿名引入的方式引入对应的包。
INI
是配置模块的默认格式,同时它支持使用include
的方式,加载多个配置文件。
app.ini
:
appname = beepkg
httpaddr = "127.0.0.1"
httpport = 9090
include "app2.ini"
app2.ini
:
runmode ="dev"
autorender = false
recoverpanic = false
viewspath = "myview"
[dev]
httpport = 8080
[prod]
httpport = 8088
[test]
httpport = 8888
package main
import (
"github.com/beego/beego/v2/core/config"
"log"
)
func main() {
cfg, err := config.NewConfig("ini", "../conf/app.ini")
if err != nil {
log.Fatalln(err)
}
val, _ := cfg.String("viewspath")
// 2023/02/19 20:19:40 auto load config viewspath is myview
log.Println("auto load config viewspath is", val)
}
JSON
只需要指定格式,并且不要忘了使用匿名引入的方式引入 JSON 的实现:
{
"app": {
"name": "myapp",
"port": "8080"
}
}
package main
import (
"github.com/beego/beego/v2/core/config"
// 千万不要忘了
_ "github.com/beego/beego/v2/core/config/json"
"github.com/beego/beego/v2/core/logs"
)
var (
ConfigFile = "../conf/app.json"
)
func main() {
err := config.InitGlobalInstance("json", ConfigFile)
if err != nil {
logs.Critical("An error occurred:", err)
panic(err)
}
val, _ := config.String("app::name")
// 2023/02/19 20:24:15.969 [I] load config appname is myproject
logs.Info("load config appname is", val)
}
别忘了匿名引入YAML
的实现:
app:
name: myapp
port: 8080
package main
import (
"github.com/beego/beego/v2/core/config"
// never forget this
_ "github.com/beego/beego/v2/core/config/yaml"
"github.com/beego/beego/v2/core/logs"
)
var (
ConfigFile = "../conf/app.yaml"
)
func main() {
err := config.InitGlobalInstance("yaml", ConfigFile)
if err != nil {
logs.Critical("An error occurred:", err)
panic(err)
}
val, _ := config.String("app.name")
// 2023/02/19 20:31:27.837 [I] load config name is myapp
logs.Info("load config name is", val)
}
别忘了匿名引入 XML
的实现,要注意,所有的配置项都要放在config
这个顶级节点之内:
# 从源码上看xml目前不支持嵌套
<?xml version="1.0" encoding="UTF-8" ?>
<config>
<app>
<name>myapp</name>
<port>8080</port>
</app>
</config>
<?xml version="1.0" encoding="UTF-8" ?>
<config>
<name>myapp</name>
<port>8080</port>
</config>
package main
import (
"github.com/beego/beego/v2/core/config"
// never forget this
_ "github.com/beego/beego/v2/core/config/xml"
"github.com/beego/beego/v2/core/logs"
)
var (
ConfigFile = "../conf/app.xml"
)
func main() {
err := config.InitGlobalInstance("xml", ConfigFile)
if err != nil {
logs.Critical("An error occurred:", err)
panic(err)
}
val, _ := config.String("name")
// 2023/02/19 20:49:16.509 [I] load config name is myapp
logs.Info("load config name is", val)
}
别忘了匿名引入 TOML
的实现:
[app]
name = "myapp"
port = 8080
package main
import (
"github.com/beego/beego/v2/core/config"
// never forget this
_ "github.com/beego/beego/v2/core/config/toml"
"github.com/beego/beego/v2/core/logs"
)
var (
ConfigFile = "../conf/app.toml"
)
func main() {
err := config.InitGlobalInstance("toml", ConfigFile)
if err != nil {
logs.Critical("An error occurred:", err)
panic(err)
}
val, _ := config.String("app.name")
logs.Info("load config name is", val)
}
别忘了匿名引入 ETCD
的实现:
package main
import (
"github.com/beego/beego/v2/core/config"
_ "github.com/beego/beego/v2/core/config/etcd"
"log"
)
func main() {
err := config.InitGlobalInstance("etcd", `{"endpoints": ["127.0.0.1:2379"],"username": "root"}`)
if err != nil {
log.Println("An error occurred:", err)
}
val, _ := config.String("username")
log.Println("load config username is", val)
}
InitGlobalInstance(name string, cfg string)
中 cfg
是一个 JSON 配置,它对应于:
type Config struct {
// Endpoints is a list of URLs.
Endpoints []string `json:"endpoints"`
// AutoSyncInterval is the interval to update endpoints with its latest members.
// 0 disables auto-sync. By default auto-sync is disabled.
AutoSyncInterval time.Duration `json:"auto-sync-interval"`
// DialTimeout is the timeout for failing to establish a connection.
DialTimeout time.Duration `json:"dial-timeout"`
// DialKeepAliveTime is the time after which client pings the server to see if
// transport is alive.
DialKeepAliveTime time.Duration `json:"dial-keep-alive-time"`
// DialKeepAliveTimeout is the time that the client waits for a response for the
// keep-alive probe. If the response is not received in this time, the connection is closed.
DialKeepAliveTimeout time.Duration `json:"dial-keep-alive-timeout"`
// MaxCallSendMsgSize is the client-side request send limit in bytes.
// If 0, it defaults to 2.0 MiB (2 * 1024 * 1024).
// Make sure that "MaxCallSendMsgSize" < server-side default send/recv limit.
// ("--max-request-bytes" flag to etcd or "embed.Config.MaxRequestBytes").
MaxCallSendMsgSize int
// MaxCallRecvMsgSize is the client-side response receive limit.
// If 0, it defaults to "math.MaxInt32", because range response can
// easily exceed request send limits.
// Make sure that "MaxCallRecvMsgSize" >= server-side default send/recv limit.
// ("--max-request-bytes" flag to etcd or "embed.Config.MaxRequestBytes").
MaxCallRecvMsgSize int
// TLS holds the client secure credentials, if any.
TLS *tls.Config
// Username is a user name for authentication.
Username string `json:"username"`
// Password is a password for authentication.
Password string `json:"password"`
// RejectOldCluster when set will refuse to create a client against an outdated cluster.
RejectOldCluster bool `json:"reject-old-cluster"`
// DialOptions is a list of dial options for the grpc client (e.g., for interceptors).
// For example, pass "grpc.WithBlock()" to block until the underlying connection is up.
// Without this, Dial returns immediately and connecting the server happens in background.
DialOptions []grpc.DialOption
// Context is the default client context; it can be used to cancel grpc dial out and
// other operations that do not have an explicit context.
Context context.Context
// Logger sets client-side logger.
// If nil, fallback to building LogConfig.
Logger *zap.Logger
// LogConfig configures client-side logger.
// If nil, use the default logger.
// TODO: configure gRPC logger
LogConfig *zap.Config
// PermitWithoutStream when set will allow client to send keepalive pings to server without any active streams(RPCs).
PermitWithoutStream bool `json:"permit-without-stream"`
// TODO: support custom balancer picker
}