mirror of
https://github.com/alibaba/higress.git
synced 2026-06-07 03:37:28 +08:00
feat: support frontend-gray plugin's envoy.yaml file to host HTML (#1343)
Co-authored-by: Kent Dong <ch3cho@qq.com>
This commit is contained in:
@@ -55,6 +55,7 @@ type GrayConfig struct {
|
|||||||
GraySubKey string
|
GraySubKey string
|
||||||
Rules []*GrayRule
|
Rules []*GrayRule
|
||||||
Rewrite *Rewrite
|
Rewrite *Rewrite
|
||||||
|
Html string
|
||||||
BaseDeployment *Deployment
|
BaseDeployment *Deployment
|
||||||
GrayDeployments []*Deployment
|
GrayDeployments []*Deployment
|
||||||
BackendGrayTag string
|
BackendGrayTag string
|
||||||
@@ -84,6 +85,7 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) {
|
|||||||
grayConfig.GraySubKey = json.Get("graySubKey").String()
|
grayConfig.GraySubKey = json.Get("graySubKey").String()
|
||||||
grayConfig.BackendGrayTag = json.Get("backendGrayTag").String()
|
grayConfig.BackendGrayTag = json.Get("backendGrayTag").String()
|
||||||
grayConfig.UserStickyMaxAge = json.Get("userStickyMaxAge").String()
|
grayConfig.UserStickyMaxAge = json.Get("userStickyMaxAge").String()
|
||||||
|
grayConfig.Html = json.Get("html").String()
|
||||||
|
|
||||||
if grayConfig.UserStickyMaxAge == "" {
|
if grayConfig.UserStickyMaxAge == "" {
|
||||||
// 默认值2天
|
// 默认值2天
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ static_resources:
|
|||||||
"<script>console.log('hello world after2')</script>"
|
"<script>console.log('hello world after2')</script>"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"html": "<!DOCTYPE html>\n <html lang=\"zh-CN\">\n<head>\n<title>app1</title>\n<meta charset=\"utf-8\" />\n</head>\n<body>\n\t测试替换html版本\n\t<br />\n\t版本: {version}\n\t<br />\n\t<script src=\"./{version}/a.js\"></script>\n</body>\n</html>"
|
||||||
}
|
}
|
||||||
- name: envoy.filters.http.router
|
- name: envoy.filters.http.router
|
||||||
typed_config:
|
typed_config:
|
||||||
@@ -116,7 +117,6 @@ static_resources:
|
|||||||
- name: httpbin
|
- name: httpbin
|
||||||
connect_timeout: 30s
|
connect_timeout: 30s
|
||||||
type: LOGICAL_DNS
|
type: LOGICAL_DNS
|
||||||
# Comment out the following line to test on v6 networks
|
|
||||||
dns_lookup_family: V4_ONLY
|
dns_lookup_family: V4_ONLY
|
||||||
lb_policy: ROUND_ROBIN
|
lb_policy: ROUND_ROBIN
|
||||||
load_assignment:
|
load_assignment:
|
||||||
|
|||||||
@@ -184,12 +184,33 @@ func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, b
|
|||||||
isPageRequest = false // 默认值
|
isPageRequest = false // 默认值
|
||||||
}
|
}
|
||||||
frontendVersion := ctx.GetContext(config.XPreHigressTag).(string)
|
frontendVersion := ctx.GetContext(config.XPreHigressTag).(string)
|
||||||
|
|
||||||
isNotFound, ok := ctx.GetContext(config.IsNotFound).(bool)
|
isNotFound, ok := ctx.GetContext(config.IsNotFound).(bool)
|
||||||
if !ok {
|
if !ok {
|
||||||
isNotFound = false // 默认值
|
isNotFound = false // 默认值
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否存在自定义 HTML, 如有则省略 rewrite.indexRouting 的内容
|
||||||
|
if grayConfig.Html != "" {
|
||||||
|
log.Debugf("Returning custom HTML from config.")
|
||||||
|
// 替换响应体为 config.Html 内容
|
||||||
|
if err := proxywasm.ReplaceHttpResponseBody([]byte(grayConfig.Html)); err != nil {
|
||||||
|
log.Errorf("Error replacing response body: %v", err)
|
||||||
|
return types.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
newHtml := util.InjectContent(grayConfig.Html, grayConfig.Injection)
|
||||||
|
// 替换当前html加载的动态文件版本
|
||||||
|
newHtml = strings.ReplaceAll(newHtml, "{version}", frontendVersion)
|
||||||
|
|
||||||
|
// 最终替换响应体
|
||||||
|
if err := proxywasm.ReplaceHttpResponseBody([]byte(newHtml)); err != nil {
|
||||||
|
log.Errorf("Error replacing injected response body: %v", err)
|
||||||
|
return types.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
if isPageRequest && isNotFound && grayConfig.Rewrite.Host != "" && grayConfig.Rewrite.NotFound != "" {
|
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})
|
||||||
|
|
||||||
@@ -204,30 +225,13 @@ func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, b
|
|||||||
// 将原始字节转换为字符串
|
// 将原始字节转换为字符串
|
||||||
newBody := string(body)
|
newBody := string(body)
|
||||||
|
|
||||||
// 收集需要插入的内容
|
newBody = util.InjectContent(newBody, grayConfig.Injection)
|
||||||
headInjection := strings.Join(grayConfig.Injection.Head, "\n")
|
|
||||||
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 {
|
if err := proxywasm.ReplaceHttpResponseBody([]byte(newBody)); err != nil {
|
||||||
return types.ActionContinue
|
return types.ActionContinue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.ActionContinue
|
return types.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -278,3 +278,28 @@ func FilterGrayWeight(grayConfig *config.GrayConfig, preVersion string, preUniqu
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InjectContent 用于将内容注入到 HTML 文档的指定位置
|
||||||
|
func InjectContent(originalHtml string, injectionConfig *config.Injection) string {
|
||||||
|
|
||||||
|
headInjection := strings.Join(injectionConfig.Head, "\n")
|
||||||
|
bodyFirstInjection := strings.Join(injectionConfig.Body.First, "\n")
|
||||||
|
bodyLastInjection := strings.Join(injectionConfig.Body.Last, "\n")
|
||||||
|
|
||||||
|
// 使用 strings.Builder 来提高性能
|
||||||
|
var sb strings.Builder
|
||||||
|
// 预分配内存,避免多次内存分配
|
||||||
|
sb.Grow(len(originalHtml) + len(headInjection) + len(bodyFirstInjection) + len(bodyLastInjection))
|
||||||
|
sb.WriteString(originalHtml)
|
||||||
|
|
||||||
|
modifiedHtml := sb.String()
|
||||||
|
|
||||||
|
// 注入到头部
|
||||||
|
modifiedHtml = strings.ReplaceAll(modifiedHtml, "</head>", headInjection + "\n</head>")
|
||||||
|
// 注入到body头
|
||||||
|
modifiedHtml = strings.ReplaceAll(modifiedHtml, "<body>", "<body>\n" + bodyFirstInjection)
|
||||||
|
// 注入到body尾
|
||||||
|
modifiedHtml = strings.ReplaceAll(modifiedHtml, "</body>", bodyLastInjection + "\n</body>")
|
||||||
|
|
||||||
|
return modifiedHtml
|
||||||
|
}
|
||||||
|
|||||||
@@ -122,3 +122,22 @@ func TestFilterGrayWeight(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReplaceHtml(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
}{
|
||||||
|
{"demo", `{"injection":{"head":["<script>console.log('Head')</script>"],"body":{"first":["<script>console.log('BodyFirst')</script>"],"last":["<script>console.log('BodyLast')</script>"]},"last":["<script>console.log('BodyLast')</script>"]},"html": "<!DOCTYPE html>\n <html lang=\"zh-CN\">\n<head>\n<title>app1</title>\n<meta charset=\"utf-8\" />\n</head>\n<body>\n\t测试替换html版本\n\t<br />\n\t版本: {version}\n\t<br />\n\t<script src=\"./{version}/a.js\"></script>\n</body>\n</html>"}`},
|
||||||
|
{"demo-noBody", `{"injection":{"head":["<script>console.log('Head')</script>"],"body":{"first":["<script>console.log('BodyFirst')</script>"],"last":["<script>console.log('BodyLast')</script>"]},"last":["<script>console.log('BodyLast')</script>"]},"html": "<!DOCTYPE html>\n <html lang=\"zh-CN\">\n<head>\n<title>app1</title>\n<meta charset=\"utf-8\" />\n</head>\n</html>"}`},
|
||||||
|
}
|
||||||
|
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 := InjectContent(grayConfig.Html, grayConfig.Injection)
|
||||||
|
t.Logf("result-----: %v", result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user