mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 04:37:31 +08:00
[frontend-gray] Increase gray types according to the ratio-weight gray (#1291)
This commit is contained in:
@@ -17,11 +17,15 @@ description: 前端灰度插件配置参考
|
|||||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||||
|----------------|--------------|----|-----|----------------------------------------------------------------------------------------------------|
|
|----------------|--------------|----|-----|----------------------------------------------------------------------------------------------------|
|
||||||
| `grayKey` | string | 非必填 | - | 用户ID的唯一标识,可以来自Cookie或者Header中,比如 userid,如果没有填写则使用`rules[].grayTagKey`和`rules[].grayTagValue`过滤灰度规则 |
|
| `grayKey` | string | 非必填 | - | 用户ID的唯一标识,可以来自Cookie或者Header中,比如 userid,如果没有填写则使用`rules[].grayTagKey`和`rules[].grayTagValue`过滤灰度规则 |
|
||||||
| `graySubKey` | string | 非必填 | - | 用户身份信息可能以JSON形式透出,比如:`userInfo:{ userCode:"001" }`,当前例子`graySubKey`取值为`userCode` |
|
| `graySubKey` | string | 非必填 | - | 用户身份信息可能以JSON形式透出,比如:`userInfo:{ userCode:"001" }`,当前例子`graySubKey`取值为`userCode` |
|
||||||
| `rules` | array of object | 必填 | - | 用户定义不同的灰度规则,适配不同的灰度场景 |
|
| `userStickyMaxAge` | int | 非必填 | 172800 | 用户粘滞的时长:单位为秒,默认为`172800`,2天时间 |
|
||||||
| `rewrite` | object | 必填 | - | 重写配置,一般用于OSS/CDN前端部署的重写配置 |
|
| `rules` | array of object | 必填 | - | 用户定义不同的灰度规则,适配不同的灰度场景 |
|
||||||
| `baseDeployment` | object | 非必填 | - | 配置Base基线规则的配置 |
|
| `rewrite` | object | 必填 | - | 重写配置,一般用于OSS/CDN前端部署的重写配置 |
|
||||||
| `grayDeployments` | array of object | 非必填 | - | 配置Gray灰度的生效规则,以及生效版本 |
|
| `baseDeployment` | object | 非必填 | - | 配置Base基线规则的配置 |
|
||||||
|
| `grayDeployments` | array of object | 非必填 | - | 配置Gray灰度的生效规则,以及生效版本 |
|
||||||
|
| `backendGrayTag` | string | 非必填 | `x-mse-tag` | 后端灰度版本Tag,如果配置了,cookie中将携带值为`${backendGrayTag}:${grayDeployments[].backendVersion}` |
|
||||||
|
| `injection` | object | 非必填 | - | 往首页HTML中注入全局信息,比如`<script>window.global = {...}</script>` |
|
||||||
|
|
||||||
|
|
||||||
`rules`字段配置说明:
|
`rules`字段配置说明:
|
||||||
|
|
||||||
@@ -56,12 +60,30 @@ description: 前端灰度插件配置参考
|
|||||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||||
|--------|--------|------|-----|-------------------------------------------------|
|
|--------|--------|------|-----|-------------------------------------------------|
|
||||||
| `version` | string | 必填 | - | Gray版本的版本号,如果命中灰度规则,则使用此版本。如果是非CDN部署,在header添加`x-higress-tag` |
|
| `version` | string | 必填 | - | Gray版本的版本号,如果命中灰度规则,则使用此版本。如果是非CDN部署,在header添加`x-higress-tag` |
|
||||||
| `backendVersion` | string | 必填 | - | 后端灰度版本,会在`XHR/Fetch`请求的header头添加 `x-mse-tag`到后端 |
|
| `backendVersion` | string | 必填 | - | 后端灰度版本,配合`key`为`${backendGrayTag}`,写入cookie中 |
|
||||||
| `name` | string | 必填 | - | 规则名称和`rules[].name`关联, |
|
| `name` | string | 必填 | - | 规则名称和`rules[].name`关联, |
|
||||||
| `enabled` | boolean | 必填 | - | 是否启动当前灰度规则 |
|
| `enabled` | boolean | 必填 | - | 是否启动当前灰度规则 |
|
||||||
|
| `weight` | int | 非必填 | - | 按照比例灰度,比如`50`。注意:灰度规则权重总和不能超过100,如果同时配置了`grayKey`以及`grayDeployments[0].weight`按照比例灰度优先生效 |
|
||||||
|
> 为了实现按比例(weight) 进行灰度发布,并确保用户粘滞,我们需要确认客户端的唯一性。如果配置了 grayKey,则将其用作唯一标识;如果未配置 grayKey,则使用客户端的访问 IP 地址作为唯一标识。
|
||||||
|
|
||||||
|
|
||||||
|
`injection`字段配置说明:
|
||||||
|
|
||||||
|
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||||
|
|--------|--------|------|-----|-------------------------------------------------|
|
||||||
|
| `head` | array of string | 非必填 | - | 注入head信息,比如`<link rel="stylesheet" href="https://cdn.example.com/styles.css">` |
|
||||||
|
| `body` | object | 非必填 | - | 注入Body |
|
||||||
|
|
||||||
|
`injection.body`字段配置说明:
|
||||||
|
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||||
|
|--------|--------|------|-----|-------------------------------------------------|
|
||||||
|
| `first` | array of string | 非必填 | - | 注入body标签的首部 |
|
||||||
|
| `after` | array of string | 非必填 | - | 注入body标签的尾部 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 配置示例
|
## 配置示例
|
||||||
### 基础配置
|
### 基础配置(按用户灰度)
|
||||||
```yml
|
```yml
|
||||||
grayKey: userid
|
grayKey: userid
|
||||||
rules:
|
rules:
|
||||||
@@ -94,6 +116,24 @@ cookie中的用户唯一标识为 `userid`,当前灰度规则配置了`beta-us
|
|||||||
|
|
||||||
否则使用`version: base`版本
|
否则使用`version: base`版本
|
||||||
|
|
||||||
|
### 按比例灰度
|
||||||
|
```yml
|
||||||
|
grayKey: userid
|
||||||
|
rules:
|
||||||
|
- name: inner-user
|
||||||
|
grayKeyValue:
|
||||||
|
- '00000001'
|
||||||
|
- '00000005'
|
||||||
|
baseDeployment:
|
||||||
|
version: base
|
||||||
|
grayDeployments:
|
||||||
|
- name: beta-user
|
||||||
|
version: gray
|
||||||
|
enabled: true
|
||||||
|
weight: 80
|
||||||
|
```
|
||||||
|
总的灰度规则为100%,其中灰度版本的权重为`80%`,基线版本为`20%`。一旦用户命中了灰度规则,会根据IP固定这个用户的灰度版本(否则会在下次请求时随机选择一个灰度版本)。
|
||||||
|
|
||||||
### 用户信息存在JSON中
|
### 用户信息存在JSON中
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
@@ -174,3 +214,31 @@ grayDeployments:
|
|||||||
- `/app1/js/a.js` => `/mfe/app1/v1.0.0/js/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`
|
- `/app1/js/template/a.js` => `/mfe/app1/v1.0.0/js/template/a.js`
|
||||||
|
|
||||||
|
|
||||||
|
### 往HTML首页注入代码
|
||||||
|
```yml
|
||||||
|
grayKey: userid
|
||||||
|
rules:
|
||||||
|
- name: inner-user
|
||||||
|
grayKeyValue:
|
||||||
|
- '00000001'
|
||||||
|
- '00000005'
|
||||||
|
baseDeployment:
|
||||||
|
version: base
|
||||||
|
grayDeployments:
|
||||||
|
- name: beta-user
|
||||||
|
version: gray
|
||||||
|
enabled: true
|
||||||
|
weight: 80
|
||||||
|
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>
|
||||||
|
```
|
||||||
|
通过 `injection`往HTML首页注入代码,可以在`head`标签注入代码,也可以在`body`标签的`first`和`last`位置注入代码。
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
XHigressTag = "x-higress-tag"
|
XHigressTag = "x-higress-tag"
|
||||||
XPreHigressTag = "x-pre-higress-tag"
|
XUniqueClientId = "x-unique-client"
|
||||||
XMseTag = "x-mse-tag"
|
XPreHigressTag = "x-pre-higress-tag"
|
||||||
IsHTML = "is_html"
|
IsPageRequest = "is-page-request"
|
||||||
IsIndex = "is_index"
|
IsNotFound = "is-not-found"
|
||||||
NotFound = "not_found"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogInfo func(format string, args ...interface{})
|
type LogInfo func(format string, args ...interface{})
|
||||||
@@ -22,16 +23,12 @@ type GrayRule struct {
|
|||||||
GrayTagValue []string
|
GrayTagValue []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseDeployment struct {
|
type Deployment struct {
|
||||||
Name string
|
|
||||||
Version string
|
|
||||||
}
|
|
||||||
|
|
||||||
type GrayDeployment struct {
|
|
||||||
Name string
|
Name string
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Version string
|
Version string
|
||||||
BackendVersion string
|
BackendVersion string
|
||||||
|
Weight int
|
||||||
}
|
}
|
||||||
|
|
||||||
type Rewrite struct {
|
type Rewrite struct {
|
||||||
@@ -41,13 +38,27 @@ type Rewrite struct {
|
|||||||
File map[string]string
|
File map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Injection struct {
|
||||||
|
Head []string
|
||||||
|
Body *BodyInjection
|
||||||
|
}
|
||||||
|
|
||||||
|
type BodyInjection struct {
|
||||||
|
First []string
|
||||||
|
Last []string
|
||||||
|
}
|
||||||
|
|
||||||
type GrayConfig struct {
|
type GrayConfig struct {
|
||||||
GrayKey string
|
UserStickyMaxAge string
|
||||||
GraySubKey string
|
TotalGrayWeight int
|
||||||
Rules []*GrayRule
|
GrayKey string
|
||||||
Rewrite *Rewrite
|
GraySubKey string
|
||||||
BaseDeployment *BaseDeployment
|
Rules []*GrayRule
|
||||||
GrayDeployments []*GrayDeployment
|
Rewrite *Rewrite
|
||||||
|
BaseDeployment *Deployment
|
||||||
|
GrayDeployments []*Deployment
|
||||||
|
BackendGrayTag string
|
||||||
|
Injection *Injection
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertToStringList(results []gjson.Result) []string {
|
func convertToStringList(results []gjson.Result) []string {
|
||||||
@@ -71,6 +82,17 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) {
|
|||||||
// 解析 GrayKey
|
// 解析 GrayKey
|
||||||
grayConfig.GrayKey = json.Get("grayKey").String()
|
grayConfig.GrayKey = json.Get("grayKey").String()
|
||||||
grayConfig.GraySubKey = json.Get("graySubKey").String()
|
grayConfig.GraySubKey = json.Get("graySubKey").String()
|
||||||
|
grayConfig.BackendGrayTag = json.Get("backendGrayTag").String()
|
||||||
|
grayConfig.UserStickyMaxAge = json.Get("userStickyMaxAge").String()
|
||||||
|
|
||||||
|
if grayConfig.UserStickyMaxAge == "" {
|
||||||
|
// 默认值2天
|
||||||
|
grayConfig.UserStickyMaxAge = "172800"
|
||||||
|
}
|
||||||
|
|
||||||
|
if grayConfig.BackendGrayTag == "" {
|
||||||
|
grayConfig.BackendGrayTag = "x-mse-tag"
|
||||||
|
}
|
||||||
|
|
||||||
// 解析 Rules
|
// 解析 Rules
|
||||||
rules := json.Get("rules").Array()
|
rules := json.Get("rules").Array()
|
||||||
@@ -94,16 +116,30 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) {
|
|||||||
baseDeployment := json.Get("baseDeployment")
|
baseDeployment := json.Get("baseDeployment")
|
||||||
grayDeployments := json.Get("grayDeployments").Array()
|
grayDeployments := json.Get("grayDeployments").Array()
|
||||||
|
|
||||||
grayConfig.BaseDeployment = &BaseDeployment{
|
grayConfig.BaseDeployment = &Deployment{
|
||||||
Name: baseDeployment.Get("name").String(),
|
Name: baseDeployment.Get("name").String(),
|
||||||
Version: baseDeployment.Get("version").String(),
|
Version: strings.Trim(baseDeployment.Get("version").String(), " "),
|
||||||
}
|
}
|
||||||
for _, item := range grayDeployments {
|
for _, item := range grayDeployments {
|
||||||
grayConfig.GrayDeployments = append(grayConfig.GrayDeployments, &GrayDeployment{
|
if !item.Get("enabled").Bool() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
grayWeight := int(item.Get("weight").Int())
|
||||||
|
grayConfig.GrayDeployments = append(grayConfig.GrayDeployments, &Deployment{
|
||||||
Name: item.Get("name").String(),
|
Name: item.Get("name").String(),
|
||||||
Enabled: item.Get("enabled").Bool(),
|
Enabled: item.Get("enabled").Bool(),
|
||||||
Version: item.Get("version").String(),
|
Version: strings.Trim(item.Get("version").String(), " "),
|
||||||
BackendVersion: item.Get("backendVersion").String(),
|
BackendVersion: item.Get("backendVersion").String(),
|
||||||
|
Weight: grayWeight,
|
||||||
})
|
})
|
||||||
|
grayConfig.TotalGrayWeight += grayWeight
|
||||||
|
}
|
||||||
|
|
||||||
|
grayConfig.Injection = &Injection{
|
||||||
|
Head: convertToStringList(json.Get("injection.head").Array()),
|
||||||
|
Body: &BodyInjection{
|
||||||
|
First: convertToStringList(json.Get("injection.body.first").Array()),
|
||||||
|
Last: convertToStringList(json.Get("injection.body.last").Array()),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ static_resources:
|
|||||||
value: |
|
value: |
|
||||||
{
|
{
|
||||||
"grayKey": "userId",
|
"grayKey": "userId",
|
||||||
|
"backendGrayTag": "x-mse-tag",
|
||||||
|
"userStickyMaxAge": 172800,
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"name": "inner-user",
|
"name": "inner-user",
|
||||||
@@ -71,7 +73,7 @@ static_resources:
|
|||||||
],
|
],
|
||||||
"rewrite": {
|
"rewrite": {
|
||||||
"host": "frontend-gray-cn-shanghai.oss-cn-shanghai-internal.aliyuncs.com",
|
"host": "frontend-gray-cn-shanghai.oss-cn-shanghai-internal.aliyuncs.com",
|
||||||
"notFoundUri": "/mfe/app1/dev/404.html",
|
"notFoundUri": "/mfe/app1/{version}/333.html",
|
||||||
"indexRouting": {
|
"indexRouting": {
|
||||||
"/app1": "/mfe/app1/{version}/index.html",
|
"/app1": "/mfe/app1/{version}/index.html",
|
||||||
"/": "/mfe/app1/{version}/index.html"
|
"/": "/mfe/app1/{version}/index.html"
|
||||||
@@ -88,10 +90,25 @@ static_resources:
|
|||||||
{
|
{
|
||||||
"name": "beta-user",
|
"name": "beta-user",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"backendVersion": "beta",
|
"enabled": true,
|
||||||
"enabled": true
|
"weight": 50
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"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>"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
- name: envoy.filters.http.router
|
- name: envoy.filters.http.router
|
||||||
typed_config:
|
typed_config:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ func main() {
|
|||||||
func parseConfig(json gjson.Result, grayConfig *config.GrayConfig, log wrapper.Log) error {
|
func parseConfig(json gjson.Result, grayConfig *config.GrayConfig, log wrapper.Log) error {
|
||||||
// 解析json 为GrayConfig
|
// 解析json 为GrayConfig
|
||||||
config.JsonToGrayConfig(json, grayConfig)
|
config.JsonToGrayConfig(json, grayConfig)
|
||||||
|
log.Infof("Rewrite: %v, GrayDeployments: %v", json.Get("rewrite"), json.Get("grayDeployments"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,10 +41,12 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig,
|
|||||||
path, _ := proxywasm.GetHttpRequestHeader(":path")
|
path, _ := proxywasm.GetHttpRequestHeader(":path")
|
||||||
fetchMode, _ := proxywasm.GetHttpRequestHeader("sec-fetch-mode")
|
fetchMode, _ := proxywasm.GetHttpRequestHeader("sec-fetch-mode")
|
||||||
|
|
||||||
isIndex := util.IsIndexRequest(fetchMode, path)
|
isPageRequest := util.IsPageRequest(fetchMode, path)
|
||||||
hasRewrite := len(grayConfig.Rewrite.File) > 0 || len(grayConfig.Rewrite.Index) > 0
|
hasRewrite := len(grayConfig.Rewrite.File) > 0 || len(grayConfig.Rewrite.Index) > 0
|
||||||
grayKeyValue := util.GetGrayKey(util.ExtractCookieValueByKey(cookies, grayConfig.GrayKey), grayConfig.GraySubKey)
|
grayKeyValueByCookie := util.ExtractCookieValueByKey(cookies, grayConfig.GrayKey)
|
||||||
|
grayKeyValueByHeader, _ := proxywasm.GetHttpRequestHeader(grayConfig.GrayKey)
|
||||||
|
// 优先从cookie中获取,否则从header中获取
|
||||||
|
grayKeyValue := util.GetGrayKey(grayKeyValueByCookie, grayKeyValueByHeader, grayConfig.GraySubKey)
|
||||||
// 如果有重写的配置,则进行重写
|
// 如果有重写的配置,则进行重写
|
||||||
if hasRewrite {
|
if hasRewrite {
|
||||||
// 禁止重新路由,要在更改Header之前操作,否则会失效
|
// 禁止重新路由,要在更改Header之前操作,否则会失效
|
||||||
@@ -53,22 +56,34 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig,
|
|||||||
// 删除Accept-Encoding,避免压缩, 如果是压缩的内容,后续插件就没法处理了
|
// 删除Accept-Encoding,避免压缩, 如果是压缩的内容,后续插件就没法处理了
|
||||||
_ = proxywasm.RemoveHttpRequestHeader("Accept-Encoding")
|
_ = proxywasm.RemoveHttpRequestHeader("Accept-Encoding")
|
||||||
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
|
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
|
||||||
|
deployment := &config.Deployment{}
|
||||||
|
|
||||||
grayDeployment := util.FilterGrayRule(&grayConfig, grayKeyValue, log.Infof)
|
preVersion, preUniqueClientId := util.GetXPreHigressVersion(cookies)
|
||||||
frontendVersion := util.GetVersion(grayConfig.BaseDeployment.Version, cookies, isIndex)
|
// 客户端唯一ID,用于在按照比率灰度时候 客户访问黏贴
|
||||||
backendVersion := ""
|
uniqueClientId := grayKeyValue
|
||||||
|
if uniqueClientId == "" {
|
||||||
// 命中灰度规则
|
xForwardedFor, _ := proxywasm.GetHttpRequestHeader("X-Forwarded-For")
|
||||||
if grayDeployment != nil {
|
uniqueClientId = util.GetRealIpFromXff(xForwardedFor)
|
||||||
frontendVersion = util.GetVersion(grayDeployment.Version, cookies, isIndex)
|
|
||||||
backendVersion = grayDeployment.BackendVersion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
proxywasm.AddHttpRequestHeader(config.XHigressTag, frontendVersion)
|
// 如果没有配置比例,则进行灰度规则匹配
|
||||||
|
if isPageRequest {
|
||||||
|
log.Infof("grayConfig.TotalGrayWeight==== %v", grayConfig.TotalGrayWeight)
|
||||||
|
if grayConfig.TotalGrayWeight > 0 {
|
||||||
|
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, path, deployment.BackendVersion, preVersion, preUniqueClientId)
|
||||||
|
} else {
|
||||||
|
deployment = util.GetVersion(grayConfig, deployment, preVersion, isPageRequest)
|
||||||
|
}
|
||||||
|
proxywasm.AddHttpRequestHeader(config.XHigressTag, deployment.Version)
|
||||||
|
|
||||||
ctx.SetContext(config.XPreHigressTag, frontendVersion)
|
ctx.SetContext(config.XPreHigressTag, deployment.Version)
|
||||||
ctx.SetContext(config.XMseTag, backendVersion)
|
ctx.SetContext(grayConfig.BackendGrayTag, deployment.BackendVersion)
|
||||||
ctx.SetContext(config.IsIndex, isIndex)
|
ctx.SetContext(config.IsPageRequest, isPageRequest)
|
||||||
|
ctx.SetContext(config.XUniqueClientId, uniqueClientId)
|
||||||
|
|
||||||
rewrite := grayConfig.Rewrite
|
rewrite := grayConfig.Rewrite
|
||||||
if rewrite.Host != "" {
|
if rewrite.Host != "" {
|
||||||
@@ -77,12 +92,12 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig,
|
|||||||
|
|
||||||
if hasRewrite {
|
if hasRewrite {
|
||||||
rewritePath := path
|
rewritePath := path
|
||||||
if isIndex {
|
if isPageRequest {
|
||||||
rewritePath = util.IndexRewrite(path, frontendVersion, grayConfig.Rewrite.Index)
|
rewritePath = util.IndexRewrite(path, deployment.Version, grayConfig.Rewrite.Index)
|
||||||
} else {
|
} else {
|
||||||
rewritePath = util.PrefixFileRewrite(path, frontendVersion, grayConfig.Rewrite.File)
|
rewritePath = util.PrefixFileRewrite(path, deployment.Version, grayConfig.Rewrite.File)
|
||||||
}
|
}
|
||||||
log.Infof("rewrite path: %s %s %v", path, frontendVersion, rewritePath)
|
log.Infof("rewrite path: %s %s %v", path, deployment.Version, rewritePath)
|
||||||
proxywasm.ReplaceHttpRequestHeader(":path", rewritePath)
|
proxywasm.ReplaceHttpRequestHeader(":path", rewritePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,15 +110,34 @@ func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig,
|
|||||||
}
|
}
|
||||||
status, err := proxywasm.GetHttpResponseHeader(":status")
|
status, err := proxywasm.GetHttpResponseHeader(":status")
|
||||||
contentType, _ := proxywasm.GetHttpResponseHeader("Content-Type")
|
contentType, _ := proxywasm.GetHttpResponseHeader("Content-Type")
|
||||||
|
|
||||||
|
if grayConfig.Rewrite != nil && grayConfig.Rewrite.Host != "" {
|
||||||
|
// 删除Content-Disposition,避免自动下载文件
|
||||||
|
proxywasm.RemoveHttpResponseHeader("Content-Disposition")
|
||||||
|
}
|
||||||
|
|
||||||
|
isPageRequest, ok := ctx.GetContext(config.IsPageRequest).(bool)
|
||||||
|
if !ok {
|
||||||
|
isPageRequest = false // 默认值
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil || status != "200" {
|
if err != nil || status != "200" {
|
||||||
isIndex := ctx.GetContext(config.IsIndex)
|
|
||||||
if status == "404" {
|
if status == "404" {
|
||||||
if grayConfig.Rewrite.NotFound != "" && isIndex != nil && isIndex.(bool) {
|
if grayConfig.Rewrite.NotFound != "" && isPageRequest {
|
||||||
ctx.SetContext(config.NotFound, true)
|
ctx.SetContext(config.IsNotFound, true)
|
||||||
responseHeaders, _ := proxywasm.GetHttpResponseHeaders()
|
responseHeaders, _ := proxywasm.GetHttpResponseHeaders()
|
||||||
headersMap := util.ConvertHeaders(responseHeaders)
|
headersMap := util.ConvertHeaders(responseHeaders)
|
||||||
headersMap[":status"][0] = "200"
|
if _, ok := headersMap[":status"]; !ok {
|
||||||
headersMap["content-type"][0] = "text/html"
|
headersMap[":status"] = []string{"200"} // 如果没有初始化,设定默认值
|
||||||
|
} else {
|
||||||
|
headersMap[":status"][0] = "200" // 修改现有值
|
||||||
|
}
|
||||||
|
if _, ok := headersMap["content-type"]; !ok {
|
||||||
|
headersMap["content-type"] = []string{"text/html"} // 如果没有初始化,设定默认值
|
||||||
|
} else {
|
||||||
|
headersMap["content-type"][0] = "text/html" // 修改现有值
|
||||||
|
}
|
||||||
|
// 删除 content-length 键
|
||||||
delete(headersMap, "content-length")
|
delete(headersMap, "content-length")
|
||||||
proxywasm.ReplaceHttpResponseHeaders(util.ReconvertHeaders(headersMap))
|
proxywasm.ReplaceHttpResponseHeaders(util.ReconvertHeaders(headersMap))
|
||||||
ctx.BufferResponseBody()
|
ctx.BufferResponseBody()
|
||||||
@@ -119,24 +153,22 @@ func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig,
|
|||||||
// 删除content-length,可能要修改Response返回值
|
// 删除content-length,可能要修改Response返回值
|
||||||
proxywasm.RemoveHttpResponseHeader("Content-Length")
|
proxywasm.RemoveHttpResponseHeader("Content-Length")
|
||||||
|
|
||||||
// 删除Content-Disposition,避免自动下载文件
|
if strings.HasPrefix(contentType, "text/html") || isPageRequest {
|
||||||
proxywasm.RemoveHttpResponseHeader("Content-Disposition")
|
|
||||||
|
|
||||||
if strings.HasPrefix(contentType, "text/html") {
|
|
||||||
ctx.SetContext(config.IsHTML, true)
|
|
||||||
// 不会进去Streaming 的Body处理
|
// 不会进去Streaming 的Body处理
|
||||||
ctx.BufferResponseBody()
|
ctx.BufferResponseBody()
|
||||||
|
|
||||||
// 添加Cache-Control 头部,禁止缓存
|
proxywasm.ReplaceHttpResponseHeader("Cache-Control", "no-cache, no-store")
|
||||||
proxywasm.ReplaceHttpRequestHeader("Cache-Control", "no-cache, no-store")
|
|
||||||
|
|
||||||
frontendVersion := ctx.GetContext(config.XPreHigressTag).(string)
|
frontendVersion := ctx.GetContext(config.XPreHigressTag).(string)
|
||||||
backendVersion := ctx.GetContext(config.XMseTag).(string)
|
xUniqueClient := ctx.GetContext(config.XUniqueClientId).(string)
|
||||||
|
|
||||||
// 设置当前的前端版本
|
// 设置前端的版本
|
||||||
proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Path=/;", config.XPreHigressTag, frontendVersion))
|
proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s,%s; Max-Age=%s; Path=/;", config.XPreHigressTag, frontendVersion, xUniqueClient, grayConfig.UserStickyMaxAge))
|
||||||
// 设置后端的前端版本
|
// 设置后端的版本
|
||||||
proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Path=/;", config.XMseTag, backendVersion))
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return types.ActionContinue
|
return types.ActionContinue
|
||||||
}
|
}
|
||||||
@@ -145,26 +177,52 @@ func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, b
|
|||||||
if !util.IsGrayEnabled(grayConfig) {
|
if !util.IsGrayEnabled(grayConfig) {
|
||||||
return types.ActionContinue
|
return types.ActionContinue
|
||||||
}
|
}
|
||||||
backendVersion := ctx.GetContext(config.XMseTag)
|
isPageRequest, ok := ctx.GetContext(config.IsPageRequest).(bool)
|
||||||
isHtml := ctx.GetContext(config.IsHTML)
|
if !ok {
|
||||||
isIndex := ctx.GetContext(config.IsIndex)
|
isPageRequest = false // 默认值
|
||||||
notFoundUri := ctx.GetContext(config.NotFound)
|
}
|
||||||
if isIndex != nil && isIndex.(bool) && notFoundUri != nil && notFoundUri.(bool) && grayConfig.Rewrite.Host != "" && grayConfig.Rewrite.NotFound != "" {
|
frontendVersion := ctx.GetContext(config.XPreHigressTag).(string)
|
||||||
|
|
||||||
|
isNotFound, ok := ctx.GetContext(config.IsNotFound).(bool)
|
||||||
|
if !ok {
|
||||||
|
isNotFound = false // 默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPageRequest && isNotFound && grayConfig.Rewrite.Host != "" && grayConfig.Rewrite.NotFound != "" {
|
||||||
client := wrapper.NewClusterClient(wrapper.RouteCluster{Host: grayConfig.Rewrite.Host})
|
client := wrapper.NewClusterClient(wrapper.RouteCluster{Host: grayConfig.Rewrite.Host})
|
||||||
client.Get(grayConfig.Rewrite.NotFound, nil, func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
|
||||||
|
client.Get(strings.Replace(grayConfig.Rewrite.NotFound, "{version}", frontendVersion, -1), nil, func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||||
proxywasm.ReplaceHttpResponseBody(responseBody)
|
proxywasm.ReplaceHttpResponseBody(responseBody)
|
||||||
proxywasm.ResumeHttpResponse()
|
proxywasm.ResumeHttpResponse()
|
||||||
}, 1500)
|
}, 1500)
|
||||||
return types.ActionPause
|
return types.ActionPause
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以text/html 开头,将 cookie转到cookie
|
if isPageRequest {
|
||||||
if isHtml != nil && isHtml.(bool) && backendVersion != nil && backendVersion.(string) != "" {
|
// 将原始字节转换为字符串
|
||||||
newText := strings.ReplaceAll(string(body), "</head>", `<script>
|
newBody := string(body)
|
||||||
!function(e,t){function n(e){var n="; "+t.cookie,r=n.split("; "+e+"=");return 2===r.length?r.pop().split(";").shift():null}var r=n("x-mse-tag");if(!r)return null;var s=XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open=function(e,t,n,a,i){return this._XHR=!0,this.addEventListener("readystatechange",function(){1===this.readyState&&r&&this.setRequestHeader("x-mse-tag",r)}),s.apply(this,arguments)};var a=e.fetch;e.fetch=function(e,t){return"undefined"==typeof t&&(t={}),"undefined"==typeof t.headers&&(t.headers={}),r&&(t.headers["x-mse-tag"]=r),a.apply(this,[e,t])}}(window,document);
|
|
||||||
</script>
|
// 收集需要插入的内容
|
||||||
</head>`)
|
headInjection := strings.Join(grayConfig.Injection.Head, "\n")
|
||||||
if err := proxywasm.ReplaceHttpResponseBody([]byte(newText)); err != nil {
|
bodyFirstInjection := strings.Join(grayConfig.Injection.Body.First, "\n")
|
||||||
|
bodyLastInjection := strings.Join(grayConfig.Injection.Body.Last, "\n")
|
||||||
|
|
||||||
|
// 使用 strings.Builder 来提高性能
|
||||||
|
var sb strings.Builder
|
||||||
|
// 预分配内存,避免多次内存分配
|
||||||
|
sb.Grow(len(newBody) + len(headInjection) + len(bodyFirstInjection) + len(bodyLastInjection))
|
||||||
|
sb.WriteString(newBody)
|
||||||
|
|
||||||
|
// 进行替换
|
||||||
|
content := sb.String()
|
||||||
|
content = strings.ReplaceAll(content, "</head>", fmt.Sprintf("%s\n</head>", headInjection))
|
||||||
|
content = strings.ReplaceAll(content, "<body>", fmt.Sprintf("<body>\n%s", bodyFirstInjection))
|
||||||
|
content = strings.ReplaceAll(content, "</body>", fmt.Sprintf("%s\n</body>", bodyLastInjection))
|
||||||
|
|
||||||
|
// 最终结果
|
||||||
|
newBody = content
|
||||||
|
|
||||||
|
if err := proxywasm.ReplaceHttpResponseBody([]byte(newBody)); err != nil {
|
||||||
return types.ActionContinue
|
return types.ActionContinue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||||
|
|
||||||
@@ -14,22 +17,53 @@ import (
|
|||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func LogInfof(format string, args ...interface{}) {
|
||||||
|
format = fmt.Sprintf("[%s] %s", "frontend-gray", format)
|
||||||
|
proxywasm.LogInfof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetXPreHigressVersion(cookies string) (string, string) {
|
||||||
|
xPreHigressVersion := ExtractCookieValueByKey(cookies, config.XPreHigressTag)
|
||||||
|
preVersions := strings.Split(xPreHigressVersion, ",")
|
||||||
|
if len(preVersions) == 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
if len(preVersions) == 1 {
|
||||||
|
return preVersions[0], ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(preVersions[0]), strings.TrimSpace(preVersions[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从xff中获取真实的IP
|
||||||
|
func GetRealIpFromXff(xff string) string {
|
||||||
|
if xff != "" {
|
||||||
|
// 通常客户端的真实 IP 是 XFF 头中的第一个 IP
|
||||||
|
ips := strings.Split(xff, ",")
|
||||||
|
if len(ips) > 0 {
|
||||||
|
return strings.TrimSpace(ips[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func IsGrayEnabled(grayConfig config.GrayConfig) bool {
|
func IsGrayEnabled(grayConfig config.GrayConfig) bool {
|
||||||
// 检查是否存在重写主机
|
// 检查是否存在重写主机
|
||||||
if grayConfig.Rewrite != nil && grayConfig.Rewrite.Host != "" {
|
if grayConfig.Rewrite != nil && grayConfig.Rewrite.Host != "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查灰度部署是否为 nil 或空
|
// 检查是否存在灰度版本配置
|
||||||
grayDeployments := grayConfig.GrayDeployments
|
return len(grayConfig.GrayDeployments) > 0
|
||||||
if grayDeployments != nil && len(grayDeployments) > 0 {
|
}
|
||||||
for _, grayDeployment := range grayDeployments {
|
|
||||||
if grayDeployment.Enabled {
|
// 是否启用后端的灰度(全链路灰度)
|
||||||
return true
|
func IsBackendGrayEnabled(grayConfig config.GrayConfig) bool {
|
||||||
}
|
for _, deployment := range grayConfig.GrayDeployments {
|
||||||
|
if deployment.BackendVersion != "" {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,12 +132,11 @@ var indexSuffixes = []string{
|
|||||||
".html", ".htm", ".jsp", ".php", ".asp", ".aspx", ".erb", ".ejs", ".twig",
|
".html", ".htm", ".jsp", ".php", ".asp", ".aspx", ".erb", ".ejs", ".twig",
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsIndexRequest determines if the request is an index request
|
func IsPageRequest(fetchMode string, myPath string) bool {
|
||||||
func IsIndexRequest(fetchMode string, p string) bool {
|
|
||||||
if fetchMode == "cors" {
|
if fetchMode == "cors" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
ext := path.Ext(p)
|
ext := path.Ext(myPath)
|
||||||
return ext == "" || ContainsValue(indexSuffixes, ext)
|
return ext == "" || ContainsValue(indexSuffixes, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,22 +166,25 @@ func PrefixFileRewrite(path, version string, matchRules map[string]string) strin
|
|||||||
return filepath.Clean(newPath)
|
return filepath.Clean(newPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetVersion(version string, cookies string, isIndex bool) string {
|
func GetVersion(grayConfig config.GrayConfig, deployment *config.Deployment, xPreHigressVersion string, isPageRequest bool) *config.Deployment {
|
||||||
if isIndex {
|
if isPageRequest {
|
||||||
return version
|
return deployment
|
||||||
}
|
}
|
||||||
// 来自Cookie中的版本
|
|
||||||
cookieVersion := ExtractCookieValueByKey(cookies, config.XPreHigressTag)
|
|
||||||
// cookie 中为空,返回当前版本
|
// cookie 中为空,返回当前版本
|
||||||
if cookieVersion == "" {
|
if xPreHigressVersion == "" {
|
||||||
return version
|
return deployment
|
||||||
}
|
}
|
||||||
|
|
||||||
// cookie 中和当前版本不相同,返回cookie中值
|
// cookie 中和当前版本不相同,返回cookie中值
|
||||||
if cookieVersion != version {
|
if xPreHigressVersion != deployment.Version {
|
||||||
return cookieVersion
|
deployments := append(grayConfig.GrayDeployments, grayConfig.BaseDeployment)
|
||||||
|
for _, curDeployment := range deployments {
|
||||||
|
if curDeployment.Version == xPreHigressVersion {
|
||||||
|
return curDeployment
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return version
|
return grayConfig.BaseDeployment
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从cookie中解析出灰度信息
|
// 从cookie中解析出灰度信息
|
||||||
@@ -169,7 +205,12 @@ func getBySubKey(grayInfoStr string, graySubKey string) string {
|
|||||||
return value.String()
|
return value.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGrayKey(grayKeyValue string, graySubKey string) string {
|
func GetGrayKey(grayKeyValueByCookie string, grayKeyValueByHeader string, graySubKey string) string {
|
||||||
|
grayKeyValue := grayKeyValueByCookie
|
||||||
|
if grayKeyValueByCookie == "" {
|
||||||
|
grayKeyValue = grayKeyValueByHeader
|
||||||
|
}
|
||||||
|
|
||||||
// 如果有子key, 尝试从子key中获取值
|
// 如果有子key, 尝试从子key中获取值
|
||||||
if graySubKey != "" {
|
if graySubKey != "" {
|
||||||
subKeyValue := getBySubKey(grayKeyValue, graySubKey)
|
subKeyValue := getBySubKey(grayKeyValue, graySubKey)
|
||||||
@@ -181,18 +222,13 @@ func GetGrayKey(grayKeyValue string, graySubKey string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FilterGrayRule 过滤灰度规则
|
// FilterGrayRule 过滤灰度规则
|
||||||
func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string, logInfof func(format string, args ...interface{})) *config.GrayDeployment {
|
func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string) *config.Deployment {
|
||||||
for _, grayDeployment := range grayConfig.GrayDeployments {
|
for _, deployment := range grayConfig.GrayDeployments {
|
||||||
if !grayDeployment.Enabled {
|
grayRule := GetRule(grayConfig.Rules, deployment.Name)
|
||||||
// 跳过Enabled=false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
grayRule := GetRule(grayConfig.Rules, grayDeployment.Name)
|
|
||||||
// 首先:先校验用户名单ID
|
// 首先:先校验用户名单ID
|
||||||
if grayRule.GrayKeyValue != nil && len(grayRule.GrayKeyValue) > 0 && grayKeyValue != "" {
|
if grayRule.GrayKeyValue != nil && len(grayRule.GrayKeyValue) > 0 && grayKeyValue != "" {
|
||||||
if ContainsValue(grayRule.GrayKeyValue, grayKeyValue) {
|
if ContainsValue(grayRule.GrayKeyValue, grayKeyValue) {
|
||||||
logInfof("frontendVersion: %s, grayKeyValue: %s", grayDeployment.Version, grayKeyValue)
|
return deployment
|
||||||
return grayDeployment
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 第二:校验Cookie中的 GrayTagKey
|
// 第二:校验Cookie中的 GrayTagKey
|
||||||
@@ -200,11 +236,45 @@ func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string, logInfof
|
|||||||
cookieStr, _ := proxywasm.GetHttpRequestHeader("cookie")
|
cookieStr, _ := proxywasm.GetHttpRequestHeader("cookie")
|
||||||
grayTagValue := ExtractCookieValueByKey(cookieStr, grayRule.GrayTagKey)
|
grayTagValue := ExtractCookieValueByKey(cookieStr, grayRule.GrayTagKey)
|
||||||
if ContainsValue(grayRule.GrayTagValue, grayTagValue) {
|
if ContainsValue(grayRule.GrayTagValue, grayTagValue) {
|
||||||
logInfof("frontendVersion: %s, grayTag: %s=%s", grayDeployment.Version, grayRule.GrayTagKey, grayTagValue)
|
return deployment
|
||||||
return grayDeployment
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logInfof("frontendVersion: %s, grayKeyValue: %s", grayConfig.BaseDeployment.Version, grayKeyValue)
|
return grayConfig.BaseDeployment
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterGrayWeight(grayConfig *config.GrayConfig, preVersion string, preUniqueClientId string, uniqueClientId string) *config.Deployment {
|
||||||
|
// 如果没有灰度权重,直接返回基础版本
|
||||||
|
if grayConfig.TotalGrayWeight == 0 {
|
||||||
|
return grayConfig.BaseDeployment
|
||||||
|
}
|
||||||
|
|
||||||
|
deployments := append(grayConfig.GrayDeployments, grayConfig.BaseDeployment)
|
||||||
|
LogInfof("preVersion: %s, preUniqueClientId: %s, uniqueClientId: %s", preVersion, preUniqueClientId, uniqueClientId)
|
||||||
|
// 用户粘滞,确保每个用户每次访问的都是走同一版本
|
||||||
|
if preVersion != "" && uniqueClientId == preUniqueClientId {
|
||||||
|
for _, deployment := range deployments {
|
||||||
|
if deployment.Version == preVersion {
|
||||||
|
return deployment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalWeight := 100
|
||||||
|
// 如果总权重小于100,则将基础版本也加入到总版本列表中
|
||||||
|
if grayConfig.TotalGrayWeight <= totalWeight {
|
||||||
|
grayConfig.BaseDeployment.Weight = 100 - grayConfig.TotalGrayWeight
|
||||||
|
} else {
|
||||||
|
totalWeight = grayConfig.TotalGrayWeight
|
||||||
|
}
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
randWeight := rand.Intn(totalWeight)
|
||||||
|
sumWeight := 0
|
||||||
|
for _, deployment := range deployments {
|
||||||
|
sumWeight += deployment.Weight
|
||||||
|
if randWeight < sumWeight {
|
||||||
|
return deployment
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package util
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/plugins/wasm-go/extensions/frontend-gray/config"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExtractCookieValueByKey(t *testing.T) {
|
func TestExtractCookieValueByKey(t *testing.T) {
|
||||||
@@ -80,7 +82,7 @@ func TestPrefixFileRewrite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsIndexRequest(t *testing.T) {
|
func TestIsPageRequest(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
fetchMode string
|
fetchMode string
|
||||||
p string
|
p string
|
||||||
@@ -97,8 +99,26 @@ func TestIsIndexRequest(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
testPath := test.p
|
testPath := test.p
|
||||||
t.Run(testPath, func(t *testing.T) {
|
t.Run(testPath, func(t *testing.T) {
|
||||||
output := IsIndexRequest(test.fetchMode, testPath)
|
output := IsPageRequest(test.fetchMode, testPath)
|
||||||
assert.Equal(t, test.output, output)
|
assert.Equal(t, test.output, output)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilterGrayWeight(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
}{
|
||||||
|
{"demo", `{"grayKey":"userId","rules":[{"name":"inner-user","grayKeyValue":["00000001","00000005"]},{"name":"beta-user","grayKeyValue":["noah","00000003"],"grayTagKey":"level","grayTagValue":["level3","level5"]}],"rewrite":{"host":"frontend-gray-cn-shanghai.oss-cn-shanghai-internal.aliyuncs.com","notFoundUri":"/mfe/app1/dev/404.html","indexRouting":{"/app1":"/mfe/app1/{version}/index.html","/":"/mfe/app1/{version}/index.html"},"fileRouting":{"/":"/mfe/app1/{version}","/app1":"/mfe/app1/{version}"}},"baseDeployment":{"version":"dev"},"grayDeployments":[{"name":"beta-user","version":"0.0.1","backendVersion":"beta","enabled":true,"weight":50}]}`},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
testName := test.name
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
grayConfig := &config.GrayConfig{}
|
||||||
|
config.JsonToGrayConfig(gjson.Parse(test.input), grayConfig)
|
||||||
|
result := FilterGrayWeight(grayConfig, "base", "1.0.1", "192.168.1.1")
|
||||||
|
t.Logf("result-----: %v", result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user