Day 06 / 共 20 天 · 第 2 周 配置与匹配

配置解析:ParseConfig 家族

昨天见过 ParseConfig,其实它有一大家子变体(raw/context/rule)。今天理清这些变体各自适合什么场景,以及底层怎么统一处理,让你写插件时选对那一个。

📍 你在 wasm-go 前 10 天的位置(W1 入门与生命周期 · W2 配置与匹配;W3-4 见 D11-20)
D01 全景· D02 request-block· D03 ABI· D04 三级Context· D05 SetCtx/选项 D06 配置解析· D07 RuleMatcher· D08 运行时匹配· D09 全局vs规则· D10 生命周期 W3-4 请求处理/高级
💡 进入第 2 周「配置与匹配」——延续机场类比 第 1 周你学会了怎么"装配一台安检芯片";第 2 周学它怎么读今天的规章、并按不同旅客走不同规矩。今天先看第一步——读规章(配置解析)parseConfig 就是芯片开工前把当班的"黑名单清单/报销制度"读进脑子。清单是一份 JSON,而 gjson 的看法是"别把整本手册翻译成结构体,你要查哪条就翻到哪条"——省事又快。今天认全 ParseConfig 那一家子变体,但记住 90% 的插件只用最简单那个。
L01

为什么有这么多变体

plugin_wrapper.go:61-67 定义了一堆解析函数类型:ParseConfigFuncParseRawConfigFuncParseConfigWithContextFuncParseRawConfigWithContextFuncParseRuleConfigFunc

别被吓到——本质就一件事 它们都是"把配置读进你的结构体",只是输入形式不同:拿到的是 gjson 还是原始 []byte?需不需要 PluginContext?是全局配置还是规则配置?90% 的插件用最简单的 ParseConfig(json, config) 就够。其他变体是给特殊需求准备的(比如配置不是 JSON 而是二进制、解析时需要访问 Leader 状态)。今天认全它们,但记住默认选最简单那个。
L02

四个主要变体

选项回调签名何时用
ParseConfig(json gjson.Result, config *T) error默认,配置是 JSON
ParseRawConfig(configBytes []byte, config *T) error配置非 JSON / 自己解析
ParseConfigWithContext(ctx PluginContext, json, config) error解析时要访问插件上下文
ParseOverrideConfigglobal + rule 两个函数支持规则级覆盖(Day 09)
读法:它们最终都被 Apply 统一成内部的 ParseRawConfigWithContextFuncctx.parseConfig 字段)——SDK 内部只存最"全"的那种,其余变体在 Apply 里适配(补 gjson 转换、补空 context)。对外提供多种简写、对内统一一种,是减少内部分支的好设计。
对外多种简写 → Apply → 对内统一一种 ParseConfig(json, cfg) ParseRawConfig([]byte, cfg) ParseConfigWithContext(ctx,…) ParseOverrideConfig(global,rule) Apply 适配 ctx.parseConfig RawConfigWithContextFunc
图注:四种简写只是"入口糖",Apply 都把它们补齐成最全的那一种存进 ctx.parseConfig。内部逻辑只需面对一种类型。
L03

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()      // 点号访问嵌套 + 判断存在
gjson 的哲学:不解析成结构体 传统做法要先定义一个和 JSON 结构一模一样的 struct,再反序列化。gjson 反其道——JSON 就放那儿不动,你要哪个字段就按路径 Get 现取。好处:省内存(不构造中间对象)、灵活(配置字段可有可无)、快。代价是取错路径不会编译报错(运行时才发现)。Wasm 环境看重体积和性能,gjson 是最优选。
📝 举个例子:同一份 JSON,gjson 按路径现取 配置 {"blocked_code":404,"block_urls":["a.html","b.html"],"case_sensitive":true}
json.Get("blocked_code").Int()404json.Get("block_urls").Array() → 长度 2 的 []gjson.Resultjson.Get("case_sensitive").Bool()truejson.Get("timeout").Exists()false(没配就是 false,你据此填默认值)。全程没定义任何 struct。

👶 小白:不定义结构体,那我把字段名 block_urls 拼错成 blockurls 会怎样?

👨‍🏫 老师:不会编译报错,而是运行时 Get("blockurls") 取到"空"、当成"没配"处理——规则悄悄失效。这就是 gjson 灵活的代价。所以配置字段名务必和文档核对,最好在 parseConfig 末尾加"至少要有一条规则"的校验(回忆 Day 02 的 errors.New("there is no block rules")),把这类空配置在启动时就暴露出来。

L04

raw 变体

ParseRawConfig(f):172-174):回调收到原始 []byte 而非 gjson。

读法:什么时候要 raw?当配置不是 JSON(比如是 YAML、protobuf、或自定义格式),或你想用别的库解析(如 yaml.Unmarshal)。ParseConfig 底层其实就是 ParseRawConfig 外面套了 gjson.ParseBytesApply :144-147)——所以 raw 更底层、更自由。
L05

WithContext 变体

ParseConfigWithContext(f):168-170):回调多一个 PluginContext 参数。

解析配置时为什么可能需要 context? PluginContext(Day 04)能访问 DoLeaderElectionGetFingerPrint、规则隔离开关等。如果你的插件在解析配置阶段就要判断"我是不是 Leader"、或读取插件指纹来做初始化,就用带 context 的变体。大多数插件解析时用不到这些,用普通 ParseConfig 即可。
L06

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。这是"在启动时尽早报错"的防呆设计——比运行时静默出错友好得多。
📝 错误驱动:如果没有这个 panic 会出什么事故 你定义了 type MyConfig struct{ Threshold int } 却忘了 wrapper.ParseConfig(parseMyConfig)。没有防呆时:插件照常启动,但 Threshold 永远是 0,限流阈值恒为 0——要么全放行、要么全拦,线上诡异行为、还极难排查。有了防呆:插件加载时直接 panic("the parseConfig is missing…"),你当场就知道漏注册了。把"运行时的诡异 bug"提前成"启动时的明确报错",正是这段的价值。
L07

空配置处理

OnPluginStart:637-680,明天细讲)里读配置:data, err := proxywasm.GetPluginConfiguration();若 len(data)==0hasCustomConfig 为真,会 log.Warn("config is empty, but has ParseConfigFunc")。还会校验 JSON 合法性 gjson.ValidBytes

读法:另一处防呆:你写了 parseConfig 却没配任何配置 → 警告(可能是忘配了)。还有个细节:配置里的 _plugin_id_PluginIDKey)会被提取出来 ResetID 用于日志标识,然后从配置里删掉——这样日志能标出是哪个插件实例。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 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
明天预告 · Day 07RuleMatcher 规则匹配——插件配置里的 _rules_ 是怎么回事,四种匹配类别(路由/域名/服务/路由前缀)的数据结构,以及配置怎么被拆成一条条规则。
← Day 05 Day 07 · RuleMatcher →