mirror of
https://github.com/alibaba/higress.git
synced 2026-03-06 01:20:51 +08:00
增加 useManifestAsEntry 配置支持 || Increase the useManifestAsEntry configuration support (#2499)
Co-authored-by: rinfx <yucheng.lxr@alibaba-inc.com>
This commit is contained in:
@@ -17,6 +17,7 @@ description: 前端灰度插件配置参考
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|----------------|--------------|----|-----|----------------------------------------------------------------------------------------------------|
|
||||
| `grayKey` | string | 非必填 | - | 用户ID的唯一标识,可以来自Cookie或者Header中,比如 userid,如果没有填写则使用`rules[].grayTagKey`和`rules[].grayTagValue`过滤灰度规则 |
|
||||
| `useManifestAsEntry` | boolean | 非必填 | false | 是否使用manifest作为入口。当设置为true时,系统将使用manifest文件作为应用入口,适用于微前端架构。在这种模式下,系统会根据manifest文件的内容来加载不同版本的前端资源。 |
|
||||
| `localStorageGrayKey` | string | 非必填 | - | 使用JWT鉴权方式,用户ID的唯一标识来自`localStorage`中,如果配置了当前参数,则`grayKey`失效 |
|
||||
| `graySubKey` | string | 非必填 | - | 用户身份信息可能以JSON形式透出,比如:`userInfo:{ userCode:"001" }`,当前例子`graySubKey`取值为`userCode` |
|
||||
| `storeMaxAge` | int | 非必填 | 60 * 60 * 24 * 365 | 网关设置Cookie最大存储时长:单位为秒,默认为1年 |
|
||||
|
||||
@@ -15,6 +15,7 @@ Execution Priority: `1000`
|
||||
| Name | Data Type | Required | Default | Description |
|
||||
|------|-----------|----------|---------|-------------|
|
||||
| `grayKey` | string | Optional | - | Unique user identifier from Cookie/Header (e.g., userid). If empty, uses `rules[].grayTagKey` and `rules[].grayTagValue` to filter rules. |
|
||||
| `useManifestAsEntry` | boolean | Optional | false | Whether to use manifest as entry point. When set to true, the system will use manifest file as application entry, suitable for micro-frontend architecture. In this mode, the system loads different versions of frontend resources based on manifest file content. |
|
||||
| `localStorageGrayKey` | string | Optional | - | When using JWT authentication, user ID comes from `localStorage`. Overrides `grayKey` if configured. |
|
||||
| `graySubKey` | string | Optional | - | Used when user info is in JSON format (e.g., `userInfo:{ userCode:"001" }`). In this example, `graySubKey` would be `userCode`. |
|
||||
| `storeMaxAge` | int | Optional | 31536000 | Max cookie storage duration in seconds (default: 1 year). |
|
||||
@@ -78,7 +79,7 @@ Execution Priority: `1000`
|
||||
| Name | Data Type | Required | Default | Description |
|
||||
|------|-----------|----------|---------|-------------|
|
||||
| `key` | string | Optional | `HIGRESS_CONSOLE_CONFIG` | Window global variable key. |
|
||||
| `featureKey` | string | Optional | `FEATURE_STATUS` | Rule hit status (e.g., `{"beta-user":true}`). |
|
||||
| `featureKey` | string | Optional | `FEATURE_STATUS` | Rule hit status (e.g., `{"beta-user":true,"inner-user":false}`). |
|
||||
| `value` | string | Optional | - | Custom global value. |
|
||||
| `enabled` | boolean | Optional | `false` | Enable global injection. |
|
||||
|
||||
@@ -86,7 +87,7 @@ Execution Priority: `1000`
|
||||
| Name | Data Type | Required | Default | Description |
|
||||
|------|-----------|----------|---------|-------------|
|
||||
| `first` | string[] | Optional | - | Inject at body start. |
|
||||
| `after` | string[] | Optional | - | Inject at body end. |
|
||||
| `last` | string[] | Optional | - | Inject at body end. |
|
||||
|
||||
## Configuration Examples
|
||||
### Basic Configuration (User-based)
|
||||
@@ -111,3 +112,165 @@ grayDeployments:
|
||||
- name: beta-user
|
||||
version: gray
|
||||
enabled: true
|
||||
|
||||
```
|
||||
|
||||
The unique user identifier in the cookie is `userid`, and the current grayscale rule configures the `beta-user` rule.
|
||||
|
||||
When the following conditions are met, the `version: gray` version will be used:
|
||||
- `userid` in cookie equals `00000002` or `00000003`
|
||||
- `level` in cookie equals `level3` or `level5`
|
||||
|
||||
Otherwise, the `version: base` version will be used.
|
||||
|
||||
### Percentage-based Grayscale
|
||||
```yml
|
||||
grayKey: userid
|
||||
rules:
|
||||
- name: inner-user
|
||||
grayKeyValue:
|
||||
- '00000001'
|
||||
- '00000005'
|
||||
baseDeployment:
|
||||
version: base
|
||||
grayDeployments:
|
||||
- name: beta-user
|
||||
version: gray
|
||||
enabled: true
|
||||
weight: 80
|
||||
|
||||
```
|
||||
The total grayscale rule is 100%, with the grayscale version weighted at 80% and the baseline version at 20%.
|
||||
|
||||
### User Information in JSON Format
|
||||
```yml
|
||||
grayKey: appInfo
|
||||
graySubKey: userId
|
||||
rules:
|
||||
- name: inner-user
|
||||
grayKeyValue:
|
||||
- '00000001'
|
||||
- '00000005'
|
||||
- name: beta-user
|
||||
grayKeyValue:
|
||||
- '00000002'
|
||||
- '00000003'
|
||||
grayTagKey: level
|
||||
grayTagValue:
|
||||
- level3
|
||||
- level5
|
||||
baseDeployment:
|
||||
version: base
|
||||
grayDeployments:
|
||||
- name: beta-user
|
||||
version: gray
|
||||
enabled: true
|
||||
|
||||
```
|
||||
|
||||
The cookie contains JSON data in `appInfo`, which includes the `userId` field as the unique identifier.
|
||||
The current grayscale rule configures the `beta-user` rule.
|
||||
When the following conditions are met, the `version: gray` version will be used:
|
||||
- `userid` in cookie equals `00000002` or `00000003`
|
||||
- `level` in cookie equals `level3` or `level5`
|
||||
|
||||
Otherwise, the `version: base` version will be used.
|
||||
|
||||
### User Information Stored in LocalStorage
|
||||
Since the gateway plugin needs to identify users by unique identity information, and HTTP protocol can only transmit information in headers, a script can be injected into the homepage to set user information from LocalStorage to cookies if user information is stored in LocalStorage.
|
||||
|
||||
```
|
||||
(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 Configuration
|
||||
> Generally used for CDN deployment scenarios
|
||||
```yml
|
||||
grayKey: userid
|
||||
rules:
|
||||
- name: inner-user
|
||||
grayKeyValue:
|
||||
- '00000001'
|
||||
- '00000005'
|
||||
- name: beta-user
|
||||
grayKeyValue:
|
||||
- '00000002'
|
||||
- '00000003'
|
||||
grayTagKey: level
|
||||
grayTagValue:
|
||||
- level3
|
||||
- level5
|
||||
rewrite:
|
||||
host: frontend-gray.oss-cn-shanghai-internal.aliyuncs.com
|
||||
indexRouting:
|
||||
/app1: '/mfe/app1/{version}/index.html'
|
||||
/: '/mfe/app1/{version}/index.html',
|
||||
fileRouting:
|
||||
/: '/mfe/app1/{version}'
|
||||
/app1/: '/mfe/app1/{version}'
|
||||
baseDeployment:
|
||||
version: base
|
||||
grayDeployments:
|
||||
- name: beta-user
|
||||
version: gray
|
||||
enabled: true
|
||||
|
||||
```
|
||||
|
||||
The `{version}` will be dynamically replaced with the actual version during runtime.
|
||||
|
||||
#### indexRouting: Homepage Routing Configuration
|
||||
Accessing `/app1`, `/app123`, `/app1/index.html`, `/app1/xxx`, `/xxxx` will all route to '/mfe/app1/{version}/index.html'
|
||||
|
||||
#### fileRouting: File Routing Configuration
|
||||
The following file mappings will be effective:
|
||||
- `/js/a.js` => `/mfe/app1/v1.0.0/js/a.js`
|
||||
- `/js/template/a.js` => `/mfe/app1/v1.0.0/js/template/a.js`
|
||||
- `/app1/js/a.js` => `/mfe/app1/v1.0.0/js/a.js`
|
||||
- `/app1/js/template/a.js` => `/mfe/app1/v1.0.0/js/template/a.js`
|
||||
|
||||
### Injecting Code into HTML Homepage
|
||||
```yml
|
||||
grayKey: userid
|
||||
rules:
|
||||
- name: inner-user
|
||||
grayKeyValue:
|
||||
- '00000001'
|
||||
- '00000005'
|
||||
baseDeployment:
|
||||
version: base
|
||||
grayDeployments:
|
||||
- name: beta-user
|
||||
version: gray
|
||||
enabled: true
|
||||
injection:
|
||||
head:
|
||||
- <script>console.log('Header')</script>
|
||||
body:
|
||||
first:
|
||||
- <script>console.log('hello world before')</script>
|
||||
- <script>console.log('hello world before1')</script>
|
||||
last:
|
||||
- <script>console.log('hello world after')</script>
|
||||
- <script>console.log('hello world after2')</script>
|
||||
|
||||
```
|
||||
Code can be injected into the HTML homepage through `injection`, either in the `head` tag or at the `first` and `last` positions of the `body` tag.
|
||||
|
||||
@@ -60,6 +60,7 @@ type BodyInjection struct {
|
||||
|
||||
type GrayConfig struct {
|
||||
StoreMaxAge int
|
||||
UseManifestAsEntry bool
|
||||
GrayKey string
|
||||
LocalStorageGrayKey string
|
||||
GraySubKey string
|
||||
@@ -129,6 +130,7 @@ func convertToStringMap(result gjson.Result) map[string]string {
|
||||
func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) error {
|
||||
// 解析 GrayKey
|
||||
grayConfig.LocalStorageGrayKey = json.Get("localStorageGrayKey").String()
|
||||
grayConfig.UseManifestAsEntry = json.Get("useManifestAsEntry").Bool()
|
||||
grayConfig.GrayKey = json.Get("grayKey").String()
|
||||
if grayConfig.LocalStorageGrayKey != "" {
|
||||
grayConfig.GrayKey = grayConfig.LocalStorageGrayKey
|
||||
|
||||
@@ -129,23 +129,26 @@ func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig)
|
||||
ctx.DontReadResponseBody()
|
||||
return types.ActionContinue
|
||||
}
|
||||
isIndexRequest, indexOk := ctx.GetContext(config.IsIndexRequest).(bool)
|
||||
if indexOk && isIndexRequest {
|
||||
// 首页请求强制不缓存
|
||||
proxywasm.ReplaceHttpResponseHeader("cache-control", "no-cache, no-store, max-age=0, must-revalidate")
|
||||
ctx.DontReadResponseBody()
|
||||
return types.ActionContinue
|
||||
if !grayConfig.UseManifestAsEntry {
|
||||
isIndexRequest, indexOk := ctx.GetContext(config.IsIndexRequest).(bool)
|
||||
if indexOk && isIndexRequest {
|
||||
// 首页请求强制不缓存
|
||||
proxywasm.ReplaceHttpResponseHeader("cache-control", "no-cache, no-store, max-age=0, must-revalidate")
|
||||
ctx.DontReadResponseBody()
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
isHtmlRequest, htmlOk := ctx.GetContext(config.IsHtmlRequest).(bool)
|
||||
// response 不处理非首页的请求
|
||||
if !htmlOk || !isHtmlRequest {
|
||||
ctx.DontReadResponseBody()
|
||||
return types.ActionContinue
|
||||
} else {
|
||||
// 不会进去Streaming 的Body处理
|
||||
ctx.BufferResponseBody()
|
||||
}
|
||||
}
|
||||
|
||||
isHtmlRequest, htmlOk := ctx.GetContext(config.IsHtmlRequest).(bool)
|
||||
// response 不处理非首页的请求
|
||||
if !htmlOk || !isHtmlRequest {
|
||||
ctx.DontReadResponseBody()
|
||||
return types.ActionContinue
|
||||
} else {
|
||||
// 不会进去Streaming 的Body处理
|
||||
ctx.BufferResponseBody()
|
||||
}
|
||||
// 处理HTML的首页
|
||||
status, err := proxywasm.GetHttpResponseHeader(":status")
|
||||
if grayConfig.Rewrite != nil && grayConfig.Rewrite.Host != "" {
|
||||
|
||||
@@ -304,7 +304,10 @@ func GetConditionRules(rules []*config.GrayRule, grayKeyValue string, cookie str
|
||||
}
|
||||
|
||||
func GetGrayWeightUniqueId(cookie string, uniqueGrayTag string) string {
|
||||
uniqueId := GetCookieValue(cookie, uniqueGrayTag)
|
||||
uniqueId, _ := proxywasm.GetHttpRequestHeader(uniqueGrayTag)
|
||||
if uniqueId == "" {
|
||||
uniqueId = GetCookieValue(cookie, uniqueGrayTag)
|
||||
}
|
||||
if uniqueId == "" {
|
||||
uniqueId = strings.ReplaceAll(uuid.NewString(), "-", "")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user