diff --git a/plugins/wasm-go/extensions/frontend-gray/README.md b/plugins/wasm-go/extensions/frontend-gray/README.md index 8dee32008..dba4b73c0 100644 --- a/plugins/wasm-go/extensions/frontend-gray/README.md +++ b/plugins/wasm-go/extensions/frontend-gray/README.md @@ -17,6 +17,7 @@ description: 前端灰度插件配置参考 | 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | |----------------|--------------|----|-----|----------------------------------------------------------------------------------------------------| | `grayKey` | string | 非必填 | - | 用户ID的唯一标识,可以来自Cookie或者Header中,比如 userid,如果没有填写则使用`rules[].grayTagKey`和`rules[].grayTagValue`过滤灰度规则 | +| `localStorageGrayKey` | string | 非必填 | - | 使用JWT鉴权方式,用户ID的唯一标识来自`localStorage`中,如果配置了当前参数,则`grayKey`失效 | | `graySubKey` | string | 非必填 | - | 用户身份信息可能以JSON形式透出,比如:`userInfo:{ userCode:"001" }`,当前例子`graySubKey`取值为`userCode` | | `userStickyMaxAge` | int | 非必填 | 172800 | 用户粘滞的时长:单位为秒,默认为`172800`,2天时间 | | `rules` | array of object | 必填 | - | 用户定义不同的灰度规则,适配不同的灰度场景 | @@ -168,6 +169,30 @@ cookie存在`appInfo`的JSON数据,其中包含`userId`字段为当前的唯 否则使用`version: base`版本 +### 用户信息存储在LocalStorage +由于网关插件需要识别用户为唯一身份信息,HTTP协议进行信息传输,只能在Header中传递。如果用户信息存储在LocalStorage,在首页注入一段脚本将LocalStorage中的用户信息设置到cookie中。 +``` +(function() { + var grayKey = '@@X_GRAY_KEY'; + var cookies = document.cookie.split('; ').filter(function(row) { + return row.indexOf(grayKey + '=') === 0; + }); + + try { + if (typeof localStorage !== 'undefined' && localStorage !== null) { + var storageValue = localStorage.getItem(grayKey); + var cookieValue = cookies.length > 0 ? decodeURIComponent(cookies[0].split('=')[1]) : null; + if (storageValue && storageValue.indexOf('=') < 0 && cookieValue && cookieValue !== storageValue) { + document.cookie = grayKey + '=' + encodeURIComponent(storageValue) + '; path=/;'; + window.location.reload(); + } + } + } catch (error) { + // xx + } +})(); +``` + ### rewrite重写配置 > 一般用于CDN部署场景 ```yml diff --git a/plugins/wasm-go/extensions/frontend-gray/config/config.go b/plugins/wasm-go/extensions/frontend-gray/config/config.go index de689aad2..ecfbb3a83 100644 --- a/plugins/wasm-go/extensions/frontend-gray/config/config.go +++ b/plugins/wasm-go/extensions/frontend-gray/config/config.go @@ -49,17 +49,18 @@ type BodyInjection struct { } type GrayConfig struct { - UserStickyMaxAge string - TotalGrayWeight int - GrayKey string - GraySubKey string - Rules []*GrayRule - Rewrite *Rewrite - Html string - BaseDeployment *Deployment - GrayDeployments []*Deployment - BackendGrayTag string - Injection *Injection + UserStickyMaxAge string + TotalGrayWeight int + GrayKey string + LocalStorageGrayKey string + GraySubKey string + Rules []*GrayRule + Rewrite *Rewrite + Html string + BaseDeployment *Deployment + GrayDeployments []*Deployment + BackendGrayTag string + Injection *Injection } func convertToStringList(results []gjson.Result) []string { @@ -81,7 +82,11 @@ func convertToStringMap(result gjson.Result) map[string]string { func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) { // 解析 GrayKey + grayConfig.LocalStorageGrayKey = json.Get("localStorageGrayKey").String() grayConfig.GrayKey = json.Get("grayKey").String() + if grayConfig.LocalStorageGrayKey != "" { + grayConfig.GrayKey = grayConfig.LocalStorageGrayKey + } grayConfig.GraySubKey = json.Get("graySubKey").String() grayConfig.BackendGrayTag = json.Get("backendGrayTag").String() grayConfig.UserStickyMaxAge = json.Get("userStickyMaxAge").String() diff --git a/plugins/wasm-go/extensions/frontend-gray/envoy.yaml b/plugins/wasm-go/extensions/frontend-gray/envoy.yaml index 6dabed21d..239e221bd 100644 --- a/plugins/wasm-go/extensions/frontend-gray/envoy.yaml +++ b/plugins/wasm-go/extensions/frontend-gray/envoy.yaml @@ -73,23 +73,22 @@ static_resources: ], "rewrite": { "host": "frontend-gray-cn-shanghai.oss-cn-shanghai-internal.aliyuncs.com", - "notFoundUri": "/cygtapi/{version}/333.html", "indexRouting": { - "/app1": "/cygtapi/{version}/index.html", - "/": "/cygtapi/{version}/index.html" + "/app1": "/mfe/app1/{version}/index.html", + "/": "/mfe/app1/{version}/index.html" }, "fileRouting": { - "/": "/cygtapi/{version}", - "/app1": "/cygtapi/{version}" + "/": "/mfe/app1/{version}", + "/app1": "/mfe/app1/{version}" } }, "baseDeployment": { - "version": "base" + "version": "dev" }, "grayDeployments": [ { "name": "beta-user", - "version": "gray", + "version": "0.0.1", "enabled": true } ], @@ -107,8 +106,7 @@ static_resources: "" ] } - }, - "html": "\n \n\napp1\n\n\n\n\t测试替换html版本\n\t
\n\t版本: {version}\n\t
\n\t\n\n" + } } - name: envoy.filters.http.router typed_config: diff --git a/plugins/wasm-go/extensions/frontend-gray/main.go b/plugins/wasm-go/extensions/frontend-gray/main.go index 81eb3034d..b1b5d28af 100644 --- a/plugins/wasm-go/extensions/frontend-gray/main.go +++ b/plugins/wasm-go/extensions/frontend-gray/main.go @@ -21,14 +21,13 @@ func main() { wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders), wrapper.ProcessResponseHeadersBy(onHttpResponseHeader), wrapper.ProcessResponseBodyBy(onHttpResponseBody), - wrapper.ProcessStreamingResponseBodyBy(onStreamingResponseBody), ) } func parseConfig(json gjson.Result, grayConfig *config.GrayConfig, log wrapper.Log) error { // 解析json 为GrayConfig config.JsonToGrayConfig(json, grayConfig) - log.Infof("Rewrite: %v, GrayDeployments: %v", json.Get("rewrite"), json.Get("grayDeployments")) + log.Debugf("Rewrite: %v, GrayDeployments: %v", json.Get("rewrite"), json.Get("grayDeployments")) return nil } @@ -98,15 +97,17 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig, } else { rewritePath = util.PrefixFileRewrite(path, deployment.Version, grayConfig.Rewrite.File) } - log.Infof("rewrite path: %s %s %v", path, deployment.Version, rewritePath) - proxywasm.ReplaceHttpRequestHeader(":path", rewritePath) + if path != rewritePath { + log.Infof("rewrite path:%s, rewritePath:%s, Version:%v", path, rewritePath, deployment.Version) + proxywasm.ReplaceHttpRequestHeader(":path", rewritePath) + } } - return types.ActionContinue } func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig, log wrapper.Log) types.Action { if !util.IsGrayEnabled(grayConfig) { + ctx.DontReadResponseBody() return types.ActionContinue } isPageRequest, ok := ctx.GetContext(config.IsPageRequest).(bool) @@ -117,6 +118,9 @@ func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig, if !isPageRequest { ctx.DontReadResponseBody() return types.ActionContinue + } else { + // 不会进去Streaming 的Body处理 + ctx.BufferResponseBody() } status, err := proxywasm.GetHttpResponseHeader(":status") @@ -159,8 +163,6 @@ func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig, return types.ActionContinue } - // 不会进去Streaming 的Body处理 - ctx.BufferResponseBody() proxywasm.ReplaceHttpResponseHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate") frontendVersion := ctx.GetContext(config.XPreHigressTag).(string) @@ -184,6 +186,11 @@ func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, b if !ok { isPageRequest = false // 默认值 } + // 只处理首页相关请求 + if !isPageRequest { + return types.ActionContinue + } + frontendVersion := ctx.GetContext(config.XPreHigressTag).(string) isNotFound, ok := ctx.GetContext(config.IsNotFound).(bool) if !ok { @@ -212,7 +219,8 @@ func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, b return types.ActionContinue } - if isPageRequest && isNotFound && grayConfig.Rewrite.Host != "" && grayConfig.Rewrite.NotFound != "" { + // 针对404页面处理 + if isNotFound && grayConfig.Rewrite.Host != "" && grayConfig.Rewrite.NotFound != "" { client := wrapper.NewClusterClient(wrapper.RouteCluster{Host: grayConfig.Rewrite.Host}) client.Get(strings.Replace(grayConfig.Rewrite.NotFound, "{version}", frontendVersion, -1), nil, func(statusCode int, responseHeaders http.Header, responseBody []byte) { @@ -222,20 +230,18 @@ func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, b return types.ActionPause } - if isPageRequest { - // 将原始字节转换为字符串 - newBody := string(body) - - newBody = util.InjectContent(newBody, grayConfig.Injection) - - if err := proxywasm.ReplaceHttpResponseBody([]byte(newBody)); err != nil { - return types.ActionContinue - } + // 处理响应体HTML + newBody := string(body) + newBody = util.InjectContent(newBody, grayConfig.Injection) + if grayConfig.LocalStorageGrayKey != "" { + localStr := strings.ReplaceAll(` + `, "@@X_GRAY_KEY", grayConfig.LocalStorageGrayKey) + newBody = strings.ReplaceAll(newBody, "", "\n"+localStr) + } + if err := proxywasm.ReplaceHttpResponseBody([]byte(newBody)); err != nil { + return types.ActionContinue } - return types.ActionContinue } - -func onStreamingResponseBody(ctx wrapper.HttpContext, pluginConfig config.GrayConfig, chunk []byte, isLastChunk bool, log wrapper.Log) []byte { - return chunk -} diff --git a/plugins/wasm-go/extensions/frontend-gray/util/utils.go b/plugins/wasm-go/extensions/frontend-gray/util/utils.go index 80291a2c3..e67d3e089 100644 --- a/plugins/wasm-go/extensions/frontend-gray/util/utils.go +++ b/plugins/wasm-go/extensions/frontend-gray/util/utils.go @@ -142,8 +142,22 @@ func IsPageRequest(fetchMode string, myPath string) bool { // 首页Rewrite func IndexRewrite(path, version string, matchRules map[string]string) string { - for prefix, rewrite := range matchRules { + // Create a slice of keys in matchRules and sort them by length in descending order + keys := make([]string, 0, len(matchRules)) + for prefix := range matchRules { + keys = append(keys, prefix) + } + sort.Slice(keys, func(i, j int) bool { + if len(keys[i]) != len(keys[j]) { + return len(keys[i]) > len(keys[j]) // Sort by length + } + return keys[i] < keys[j] // Sort lexicographically + }) + + // Iterate over sorted keys to find the longest match + for _, prefix := range keys { if strings.HasPrefix(path, prefix) { + rewrite := matchRules[prefix] newPath := strings.Replace(rewrite, "{version}", version, -1) return newPath } diff --git a/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go b/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go index d51e17f32..b6681c98a 100644 --- a/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go +++ b/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go @@ -53,6 +53,30 @@ func TestIndexRewrite(t *testing.T) { } } +func TestIndexRewrite2(t *testing.T) { + matchRules := map[string]string{ + "/": "/{version}/index.html", + "/sta": "/sta/{version}/index.html", + "/static": "/static/{version}/index.html", + } + + var tests = []struct { + path, output string + }{ + {"/static123", "/static/v1.0.0/index.html"}, + {"/static", "/static/v1.0.0/index.html"}, + {"/sta", "/sta/v1.0.0/index.html"}, + {"/", "/v1.0.0/index.html"}, + } + for _, test := range tests { + testName := test.path + t.Run(testName, func(t *testing.T) { + output := IndexRewrite(testName, "v1.0.0", matchRules) + assert.Equal(t, test.output, output) + }) + } +} + func TestPrefixFileRewrite(t *testing.T) { matchRules := map[string]string{ // 前缀匹配