配置解析:ParseConfig 家族
昨天见过 ParseConfig,其实它有一大家子变体(raw/context/rule)。今天理清这些变体各自适合什么场景,以及底层怎么统一处理,让你写插件时选对那一个。
parseConfig 就是芯片开工前把当班的"黑名单清单/报销制度"读进脑子。清单是一份 JSON,而 gjson 的看法是"别把整本手册翻译成结构体,你要查哪条就翻到哪条"——省事又快。今天认全 ParseConfig 那一家子变体,但记住 90% 的插件只用最简单那个。为什么有这么多变体
plugin_wrapper.go:61-67 定义了一堆解析函数类型:ParseConfigFunc、ParseRawConfigFunc、ParseConfigWithContextFunc、ParseRawConfigWithContextFunc、ParseRuleConfigFunc…
[]byte?需不需要 PluginContext?是全局配置还是规则配置?90% 的插件用最简单的 ParseConfig(json, config) 就够。其他变体是给特殊需求准备的(比如配置不是 JSON 而是二进制、解析时需要访问 Leader 状态)。今天认全它们,但记住默认选最简单那个。四个主要变体
| 选项 | 回调签名 | 何时用 |
|---|---|---|
| ParseConfig | (json gjson.Result, config *T) error | 默认,配置是 JSON |
| ParseRawConfig | (configBytes []byte, config *T) error | 配置非 JSON / 自己解析 |
| ParseConfigWithContext | (ctx PluginContext, json, config) error | 解析时要访问插件上下文 |
| ParseOverrideConfig | global + rule 两个函数 | 支持规则级覆盖(Day 09) |
Apply 统一成内部的 ParseRawConfigWithContextFunc(ctx.parseConfig 字段)——SDK 内部只存最"全"的那种,其余变体在 Apply 里适配(补 gjson 转换、补空 context)。对外提供多种简写、对内统一一种,是减少内部分支的好设计。ctx.parseConfig。内部逻辑只需面对一种类型。gjson 用法
回顾 request-block 的用法,gjson 常用套路:
json.Get("blocked_code").Int() // 取整数
json.Get("case_sensitive").Bool() // 取布尔
json.Get("blocked_message").String() // 取字符串
json.Get("block_urls").Array() // 取数组,返回 []gjson.Result
json.Get("nested.field").Exists() // 点号访问嵌套 + 判断存在
Get 现取。好处:省内存(不构造中间对象)、灵活(配置字段可有可无)、快。代价是取错路径不会编译报错(运行时才发现)。Wasm 环境看重体积和性能,gjson 是最优选。{"blocked_code":404,"block_urls":["a.html","b.html"],"case_sensitive":true}:json.Get("blocked_code").Int() → 404;json.Get("block_urls").Array() → 长度 2 的 []gjson.Result;json.Get("case_sensitive").Bool() → true;json.Get("timeout").Exists() → false(没配就是 false,你据此填默认值)。全程没定义任何 struct。👶 小白:不定义结构体,那我把字段名 block_urls 拼错成 blockurls 会怎样?
👨🏫 老师:不会编译报错,而是运行时 Get("blockurls") 取到"空"、当成"没配"处理——规则悄悄失效。这就是 gjson 灵活的代价。所以配置字段名务必和文档核对,最好在 parseConfig 末尾加"至少要有一条规则"的校验(回忆 Day 02 的 errors.New("there is no block rules")),把这类空配置在启动时就暴露出来。
raw 变体
ParseRawConfig(f)(:172-174):回调收到原始 []byte 而非 gjson。
yaml.Unmarshal)。ParseConfig 底层其实就是 ParseRawConfig 外面套了 gjson.ParseBytes(Apply :144-147)——所以 raw 更底层、更自由。WithContext 变体
ParseConfigWithContext(f)(:168-170):回调多一个 PluginContext 参数。
PluginContext(Day 04)能访问 DoLeaderElection、GetFingerPrint、规则隔离开关等。如果你的插件在解析配置阶段就要判断"我是不是 Leader"、或读取插件指纹来做初始化,就用带 context 的变体。大多数插件解析时用不到这些,用普通 ParseConfig 即可。hasCustomConfig
NewCommonVmCtxWithOptions(:532-541)里有段有意思的逻辑:
if ctx.parseConfig == nil {
var config PluginConfig
if unsafe.Sizeof(config) != 0 { // 配置类型非空
panic("the `parseConfig` is missing in NewCommonVmCtx's arguments")
}
ctx.hasCustomConfig = false // 配置类型是空 struct,允许没有 parseConfig
ctx.parseConfig = parseEmptyPluginConfig[PluginConfig]
}
ParseConfig——那配置永远解析不了,肯定是 bug。SDK 用 unsafe.Sizeof 检测:配置类型非空但没 parseConfig → 直接 panic 提醒你。但如果配置类型是空 struct(struct{},插件不需要配置),就允许省略 parseConfig。这是"在启动时尽早报错"的防呆设计——比运行时静默出错友好得多。type MyConfig struct{ Threshold int } 却忘了 wrapper.ParseConfig(parseMyConfig)。没有防呆时:插件照常启动,但 Threshold 永远是 0,限流阈值恒为 0——要么全放行、要么全拦,线上诡异行为、还极难排查。有了防呆:插件加载时直接 panic("the parseConfig is missing…"),你当场就知道漏注册了。把"运行时的诡异 bug"提前成"启动时的明确报错",正是这段的价值。空配置处理
OnPluginStart(:637-680,明天细讲)里读配置:data, err := proxywasm.GetPluginConfiguration();若 len(data)==0 且 hasCustomConfig 为真,会 log.Warn("config is empty, but has ParseConfigFunc")。还会校验 JSON 合法性 gjson.ValidBytes。
_plugin_id_(PluginIDKey)会被提取出来 ResetID 用于日志标识,然后从配置里删掉——这样日志能标出是哪个插件实例。今日小结 + 动手
🧠 今天你应该能回答
- ParseConfig 的四个主要变体各适合什么场景?
- gjson"不解析成结构体"的哲学,好处和代价?
- raw 变体和 WithContext 变体分别解决什么?
hasCustomConfig的 panic 防呆在防什么?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/wasm-go
sed -n '61,67p' pkg/wrapper/plugin_wrapper.go
sed -n '132,174p' pkg/wrapper/plugin_wrapper.go
sed -n '532,541p' pkg/wrapper/plugin_wrapper.go
_rules_ 是怎么回事,四种匹配类别(路由/域名/服务/路由前缀)的数据结构,以及配置怎么被拆成一条条规则。