Day 08 / 共 20 天 · 第 2 周 配置与匹配
运行时匹配 GetMatchConfig
昨天配置被拆成一条条规则。今天看请求真正来时,GetMatchConfig 怎么用当前请求的域名/路由/服务,从这些规则里挑出"该用哪份配置"。这是控制面配置在数据面生效的最后一环。
📍 你在 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 请求处理/高级
💡 延续机场类比:门卫拿着请求去"对号入座"查规章
昨天(D07)那本"分区规章"已经拆成一条条规则了;今天来了个真旅客,门卫要按他的"航班信息"(域名
:authority / 路由 route_name / 后端服务 cluster_name)从上往下翻规章,命中第一条就照它办——这正是"门卫查访客名单"。翻到底都没命中,就走"默认通道"(全局配置)。关键记住两点:① 匹配对插件作者透明——你的钩子直接拿到已选好的 config;② 顺序即优先级,越靠前越先命中。L01
匹配发生在哪
还记得 Day 04 说 CommonPluginCtx 内嵌了 RuleMatcher。当请求进入某个处理钩子前,SDK 会调 GetMatchConfig() 选出当前请求该用的配置,再把它作为 config 参数传给你的钩子。
你的钩子拿到的 config 是"已经匹配好的"
你写
onHttpRequestHeaders(ctx, config) 时,那个 config 已经是"针对当前请求匹配出来的正确配置"了——你不用自己判断"这个请求该用哪条规则"。SDK 在调你之前就 GetMatchConfig 选好了。这就是 RuleMatcher 内嵌在 PluginCtx 的意义:把匹配透明化,插件作者只管写业务。L02
三个运行时属性
GetMatchConfig(rule_matcher.go:122-140)开头取当前请求的三个关键信息:
host, _ := proxywasm.GetHttpRequestHeader(":authority") // 域名
routeName, _ := proxywasm.GetProperty([]string{"route_name"}) // Envoy 匹配的路由名
serviceName, _ := proxywasm.GetProperty([]string{"cluster_name"}) // 后端集群名
这三个值从哪来?
:authority 是 HTTP/2 的域名伪头(就是 Host);route_name 和 cluster_name 是 Envoy 处理这个请求时算出来的"属性"(回想 Envoy/Istio 课:Envoy 先做路由匹配,选出 route 和 cluster)。wasm-go 通过 GetProperty 从 Envoy 拿这些运行时信息——插件站在 Envoy 已经算好的结果上做二次匹配。这就是为什么 category 有 Route/Service——它们对应 Envoy 的 route/cluster。L03
逐条规则匹配
rule_matcher.go:140-176:遍历所有规则,按 category 判断是否命中:
for _, rule := range m.ruleConfig {
if rule.category == Host && m.hostMatch(rule, host) {
return &rule.config, nil
}
if rule.category == Route {
if _, ok := rule.routes[string(routeName)]; ok { return &rule.config, nil }
}
if rule.category == Service && m.serviceMatch(rule, string(serviceName)) {
return &rule.config, nil
}
if rule.category == RouteAndService {
if _, ok := rule.routes[string(routeName)]; ok && m.serviceMatch(rule, string(serviceName)) {
return &rule.config, nil
}
}
if rule.category == RoutePrefix {
for routePrefix := range rule.routePrefixs {
if strings.HasPrefix(string(routeName), routePrefix) { return &rule.config, nil }
}
}
}
读法:Route 用
map 查(O(1)),Service/Host 调专用匹配(要处理通配),RoutePrefix 遍历前缀做 HasPrefix,RouteAndService 要两个条件都满足。命中第一条就返回它的 config——所以规则顺序 = 优先级(L06)。🧍 第一人称请求之旅:现在你就是那个请求
你是一个请求,头上贴着标签:
:authority=api.ex.com、route_name=route-a、cluster_name=svc-x。你走到门卫台,门卫翻开规章从第一条开始比对你的标签——命中就当场按那条规矩放你走,谁也不再往下翻。下面这张表就是门卫翻规章的过程。图注:命中规则#2 后立刻返回,#3 和兜底都不再看。若#1~#3 全不中,才落到 globalConfig。
📝 单步走查:同样 3 条规则,换不同请求会命中谁
看第一行:规则#1 排在前面,即便请求也满足#2,也是#1 先命中——再次印证"顺序即优先级"。
| 请求(route_name / authority) | 规则#1 Host=*.ex.com | 规则#2 Route={route-a} | 最终 config |
|---|---|---|---|
| route-a / api.ex.com | 先检查:命中 | —(已返回) | 规则#1 |
| route-a / api.other.com | 不中 | 命中 | 规则#2 |
| route-z / api.other.com | 不中 | 不中 | globalConfig 兜底 |
L04
Host 匹配实现
hostMatch 按昨天的三种 MatchType 处理通配:
- Exact:域名完全相等。
- Prefix(
*.example.com):请求域名以.example.com结尾。 - Suffix(
example.*):请求域名以example.开头。
读法:通配符
* 的位置决定匹配方式。serviceMatch 同理处理服务名(Higress 的服务名带命名空间/端口后缀,可能需要前缀匹配)。这些细节 SDK 都封好了,你配 *.example.com 就自动是前缀通配。L05
兜底全局配置
rule_matcher.go:176-180:所有规则都没命中时——
if m.hasGlobalConfig {
return &m.globalConfig, nil // 用全局配置兜底
}
return nil, nil // 没有全局配置,返回 nil
}
全局配置 = 默认规则
规则匹配是"特殊情况特殊处理",没命中任何规则的请求,就用全局配置(那些写在
_rules_ 外面的顶层键)。好比"VIP 走 VIP 通道,其余走普通通道"。如果连全局配置都没有,返回 nil——你的钩子拿到 nil config 要小心处理(通常意味着这个请求不该被这个插件处理)。👶 小白:既然可能返回 nil,我的钩子里要不要每次都判空?
👨🏫 老师:要养成这个意识,但多数情况下你配了全局配置就不会为 nil。真正会 nil 的场景是"只写了 _rules_、没写任何全局键,且当前请求一条规则都不匹配"——SDK 会认为"这个插件不该管这个请求"。写关键插件时对 nil 做防御(比如直接 ActionContinue 放行)比裸用更稳妥。
L06
匹配顺序即优先级
"命中第一条即返回"的含义
GetMatchConfig 是 从上到下遍历规则,命中第一条就返回。所以规则在配置里的顺序 = 优先级:越靠前越优先。控制面(Console/Higress)生成配置时会按作用域优先级(SERVICE > ROUTE > DOMAIN,回想上一站 Day 09)排好序,让更具体的规则排在前面。这样"路由级配置覆盖域名级配置"的语义就自然实现了——具体的先命中。L07
钩子里怎么用(透明)
对插件作者,这一切是透明的。你只需:
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
// config 已经是匹配出来的正确配置,直接用
if config.enabled { /* ... */ }
return types.ActionContinue
}
读法:SDK 在调这个钩子前已经跑完
GetMatchConfig,把结果作为 config 传进来。你从不直接调 GetMatchConfig——它是 SDK 的内部机制。这正是好 SDK 的标志:复杂的匹配藏在背后,作者只见到"当前该用的配置"。L08
今日小结 + 动手
🧠 今天你应该能回答
- 匹配何时发生?钩子拿到的 config 是原始的还是匹配好的?
- 三个运行时属性(:authority/route_name/cluster_name)从哪来?
- 为什么"命中第一条即返回"意味着顺序=优先级?
- 没命中任何规则时怎么办?config 可能是 nil 吗?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/wasm-go
sed -n '122,180p' pkg/matcher/rule_matcher.go
grep -n "hostMatch\|serviceMatch" pkg/matcher/rule_matcher.go pkg/matcher/utils.go
明天预告 · Day 09:全局配置 vs 规则配置——
ParseOverrideConfig 怎么让规则配置"继承+覆盖"全局配置,以及"规则级隔离"容错机制(一条规则解析失败不影响其他,还能从备份恢复)。