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{
// 前缀匹配