diff --git a/plugins/wasm-go/extensions/frontend-gray/README.md b/plugins/wasm-go/extensions/frontend-gray/README.md index 870100eb2..c54293062 100644 --- a/plugins/wasm-go/extensions/frontend-gray/README.md +++ b/plugins/wasm-go/extensions/frontend-gray/README.md @@ -57,15 +57,17 @@ description: 前端灰度插件配置参考 | 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | |----------------|--------------|------|-----|-----------------------------------------------------------------------------------| -| `version` | string | 必填 | - | Base版本的版本号,作为兜底的版本 | +| `version` | string | 必填 | - | Base版本的版本号,作为兜底的版本 | +| `versionPredicates` | string | 必填 | - | 和`version`含义相同,但是满足多版本的需求:根据不同路由映射不同的`Version`版本。一般用于微前端的场景:一个主应用需要管理多个微应用 | `grayDeployments`字段配置说明: | 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | |--------|--------|------|-----|-------------------------------------------------| -| `version` | string | 必填 | - | Gray版本的版本号,如果命中灰度规则,则使用此版本。如果是非CDN部署,在header添加`x-higress-tag` | +| `version` | string | 必填 | - | Gray版本的版本号,如果命中灰度规则,则使用此版本。如果是非CDN部署,在header添加`x-higress-tag` | +| `versionPredicates` | string | 必填 | - | 和`version`含义相同,但是满足多版本的需求:根据不同路由映射不同的`Version`版本。一般用于微前端的场景:一个主应用需要管理多个微应用 | | `backendVersion` | string | 必填 | - | 后端灰度版本,配合`key`为`${backendGrayTag}`,写入cookie中 | -| `name` | string | 必填 | - | 规则名称和`rules[].name`关联, | +| `name` | string | 必填 | - | 规则名称和`rules[].name`关联 | | `enabled` | boolean | 必填 | - | 是否启动当前灰度规则 | | `weight` | int | 非必填 | - | 按照比例灰度,比如`50`。注意:灰度规则权重总和不能超过100,如果同时配置了`grayKey`以及`grayDeployments[0].weight`按照比例灰度优先生效 | > 为了实现按比例(weight) 进行灰度发布,并确保用户粘滞,我们需要确认客户端的唯一性。如果配置了 grayKey,则将其用作唯一标识;如果未配置 grayKey,则使用客户端的访问 IP 地址作为唯一标识。 diff --git a/plugins/wasm-go/extensions/frontend-gray/config/config.go b/plugins/wasm-go/extensions/frontend-gray/config/config.go index 25596fefe..4a56821d2 100644 --- a/plugins/wasm-go/extensions/frontend-gray/config/config.go +++ b/plugins/wasm-go/extensions/frontend-gray/config/config.go @@ -25,11 +25,12 @@ type GrayRule struct { } type Deployment struct { - Name string - Enabled bool - Version string - BackendVersion string - Weight int + Name string + Enabled bool + Version string + BackendVersion string + Weight int + VersionPredicates map[string]string } type Rewrite struct { @@ -129,8 +130,9 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) { grayDeployments := json.Get("grayDeployments").Array() grayConfig.BaseDeployment = &Deployment{ - Name: baseDeployment.Get("name").String(), - Version: strings.Trim(baseDeployment.Get("version").String(), " "), + Name: baseDeployment.Get("name").String(), + Version: strings.Trim(baseDeployment.Get("version").String(), " "), + VersionPredicates: convertToStringMap(baseDeployment.Get("versionPredicates")), } for _, item := range grayDeployments { if !item.Get("enabled").Bool() { @@ -138,11 +140,12 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) { } grayWeight := int(item.Get("weight").Int()) grayConfig.GrayDeployments = append(grayConfig.GrayDeployments, &Deployment{ - Name: item.Get("name").String(), - Enabled: item.Get("enabled").Bool(), - Version: strings.Trim(item.Get("version").String(), " "), - BackendVersion: item.Get("backendVersion").String(), - Weight: grayWeight, + Name: item.Get("name").String(), + Enabled: item.Get("enabled").Bool(), + Version: strings.Trim(item.Get("version").String(), " "), + BackendVersion: item.Get("backendVersion").String(), + Weight: grayWeight, + VersionPredicates: convertToStringMap(item.Get("versionPredicates")), }) grayConfig.TotalGrayWeight += grayWeight } diff --git a/plugins/wasm-go/extensions/frontend-gray/main.go b/plugins/wasm-go/extensions/frontend-gray/main.go index c5e738eea..2cb4a5f9f 100644 --- a/plugins/wasm-go/extensions/frontend-gray/main.go +++ b/plugins/wasm-go/extensions/frontend-gray/main.go @@ -70,22 +70,28 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig, } // 如果没有配置比例,则进行灰度规则匹配 - if isPageRequest { - if grayConfig.TotalGrayWeight > 0 { - log.Infof("grayConfig.TotalGrayWeight: %v", grayConfig.TotalGrayWeight) - deployment = util.FilterGrayWeight(&grayConfig, preVersion, preUniqueClientId, uniqueClientId) - } else { - deployment = util.FilterGrayRule(&grayConfig, grayKeyValue) - } - log.Infof("index deployment: %v, path: %v, backend: %v, xPreHigressVersion: %s,%s", deployment, requestPath, deployment.BackendVersion, preVersion, preUniqueClientId) + if util.IsSupportMultiVersion(grayConfig) { + deployment = util.FilterMultiVersionGrayRule(&grayConfig, grayKeyValue, requestPath) + log.Infof("multi version %v", deployment) } else { - grayDeployment := util.FilterGrayRule(&grayConfig, grayKeyValue) - deployment = util.GetVersion(grayConfig, grayDeployment, preVersion, isPageRequest) + if isPageRequest { + if grayConfig.TotalGrayWeight > 0 { + log.Infof("grayConfig.TotalGrayWeight: %v", grayConfig.TotalGrayWeight) + deployment = util.FilterGrayWeight(&grayConfig, preVersion, preUniqueClientId, uniqueClientId) + } else { + deployment = util.FilterGrayRule(&grayConfig, grayKeyValue) + } + log.Infof("index deployment: %v, path: %v, backend: %v, xPreHigressVersion: %s,%s", deployment, requestPath, deployment.BackendVersion, preVersion, preUniqueClientId) + } else { + grayDeployment := util.FilterGrayRule(&grayConfig, grayKeyValue) + deployment = util.GetVersion(grayConfig, grayDeployment, preVersion, isPageRequest) + } + ctx.SetContext(config.XPreHigressTag, deployment.Version) + ctx.SetContext(grayConfig.BackendGrayTag, deployment.BackendVersion) } + proxywasm.AddHttpRequestHeader(config.XHigressTag, deployment.Version) - ctx.SetContext(config.XPreHigressTag, deployment.Version) - ctx.SetContext(grayConfig.BackendGrayTag, deployment.BackendVersion) ctx.SetContext(config.IsPageRequest, isPageRequest) ctx.SetContext(config.XUniqueClientId, uniqueClientId) @@ -167,18 +173,24 @@ func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig, log.Errorf("error status: %s, error message: %v", status, err) return types.ActionContinue } + cacheControl, _ := proxywasm.GetHttpResponseHeader("cache-control") + if !strings.Contains(cacheControl, "no-cache") { + proxywasm.ReplaceHttpResponseHeader("cache-control", "no-cache, no-store, max-age=0, must-revalidate") + } - proxywasm.ReplaceHttpResponseHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate") - - frontendVersion := ctx.GetContext(config.XPreHigressTag).(string) - xUniqueClient := ctx.GetContext(config.XUniqueClientId).(string) + frontendVersion, isFeVersionOk := ctx.GetContext(config.XPreHigressTag).(string) + xUniqueClient, isUniqClientOk := ctx.GetContext(config.XUniqueClientId).(string) // 设置前端的版本 - proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s,%s; Max-Age=%s; Path=/;", config.XPreHigressTag, frontendVersion, xUniqueClient, grayConfig.UserStickyMaxAge)) + if isFeVersionOk && isUniqClientOk && frontendVersion != "" { + proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s,%s; Max-Age=%s; Path=/;", config.XPreHigressTag, frontendVersion, xUniqueClient, grayConfig.UserStickyMaxAge)) + } // 设置后端的版本 if util.IsBackendGrayEnabled(grayConfig) { - backendVersion := ctx.GetContext(grayConfig.BackendGrayTag).(string) - proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%s; Path=/;", grayConfig.BackendGrayTag, backendVersion, grayConfig.UserStickyMaxAge)) + backendVersion, isBackVersionOk := ctx.GetContext(grayConfig.BackendGrayTag).(string) + if isBackVersionOk && backendVersion != "" { + proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%s; Path=/;", grayConfig.BackendGrayTag, backendVersion, grayConfig.UserStickyMaxAge)) + } } return types.ActionContinue } @@ -188,16 +200,13 @@ func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, b if !enabledGray { return types.ActionContinue } - isPageRequest, ok := ctx.GetContext(config.IsPageRequest).(bool) - if !ok { - isPageRequest = false // 默认值 - } + isPageRequest, isPageRequestOk := ctx.GetContext(config.IsPageRequest).(bool) + frontendVersion, isFeVersionOk := ctx.GetContext(config.XPreHigressTag).(string) // 只处理首页相关请求 - if !isPageRequest { + if !isFeVersionOk || !isPageRequestOk || !isPageRequest { return types.ActionContinue } - frontendVersion := ctx.GetContext(config.XPreHigressTag).(string) isNotFound, ok := ctx.GetContext(config.IsNotFound).(bool) if !ok { isNotFound = false // 默认值 diff --git a/plugins/wasm-go/extensions/frontend-gray/util/utils.go b/plugins/wasm-go/extensions/frontend-gray/util/utils.go index da93c6821..a1a62a8fd 100644 --- a/plugins/wasm-go/extensions/frontend-gray/util/utils.go +++ b/plugins/wasm-go/extensions/frontend-gray/util/utils.go @@ -276,6 +276,43 @@ func GetGrayKey(grayKeyValueByCookie string, grayKeyValueByHeader string, graySu return grayKeyValue } +// 如果基础部署或任何灰度部署中包含VersionPredicates,则认为是多版本配置 +func IsSupportMultiVersion(grayConfig config.GrayConfig) bool { + if len(grayConfig.BaseDeployment.VersionPredicates) > 0 { + return true + } + for _, deployment := range grayConfig.GrayDeployments { + if len(deployment.VersionPredicates) > 0 { + return true + } + } + return false +} + +// FilterMultiVersionGrayRule 过滤多版本灰度规则 +func FilterMultiVersionGrayRule(grayConfig *config.GrayConfig, grayKeyValue string, requestPath string) *config.Deployment { + // 首先根据灰度键值获取当前部署 + currentDeployment := FilterGrayRule(grayConfig, grayKeyValue) + + // 创建一个新的部署对象,初始化版本为当前部署的版本 + deployment := &config.Deployment{ + Version: currentDeployment.Version, + } + + // 对版本谓词的键进行排序 + keys := SortKeysByLengthAndLexicographically(currentDeployment.VersionPredicates) + + // 遍历排序后的键 + for _, prefix := range keys { + // 如果请求路径以当前前缀开头 + if strings.HasPrefix(requestPath, prefix) { + deployment.Version = currentDeployment.VersionPredicates[prefix] + return deployment + } + } + return deployment +} + // FilterGrayRule 过滤灰度规则 func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string) *config.Deployment { for _, deployment := range grayConfig.GrayDeployments {