Files
higress/plugins/wasm-go/extensions/traffic-editor/pkg/context.go
2025-12-26 17:29:55 +08:00

311 lines
7.0 KiB
Go

package pkg
import (
"maps"
"net/url"
"strings"
"github.com/higress-group/wasm-go/pkg/log"
)
type Stage int
const (
StageInvalid Stage = iota
StageRequestHeaders
StageRequestBody
StageResponseHeaders
StageResponseBody
pathHeader = ":path"
)
var (
OrderedStages = []Stage{
StageRequestHeaders,
StageRequestBody,
StageResponseHeaders,
StageResponseBody,
}
Stage2String = map[Stage]string{
StageRequestHeaders: "request_headers",
StageRequestBody: "request_body",
StageResponseHeaders: "response_headers",
StageResponseBody: "response_body",
}
)
type EditorContext interface {
GetEffectiveCommandSet() *CommandSet
SetEffectiveCommandSet(cmdSet *CommandSet)
GetCommandExecutors() []Executor
SetCommandExecutors(executors []Executor)
GetCurrentStage() Stage
SetCurrentStage(stage Stage)
GetRequestPath() string
SetRequestPath(path string)
GetRequestHeader(key string) []string
GetRequestHeaders() map[string][]string
SetRequestHeaders(map[string][]string)
GetRequestQuery(key string) []string
GetRequestQueries() map[string][]string
SetRequestQueries(map[string][]string)
GetResponseHeader(key string) []string
GetResponseHeaders() map[string][]string
SetResponseHeaders(map[string][]string)
GetRefValue(ref *Ref) string
GetRefValues(ref *Ref) []string
SetRefValue(ref *Ref, value string)
SetRefValues(ref *Ref, values []string)
DeleteRefValues(ref *Ref)
IsRequestHeadersDirty() bool
IsResponseHeadersDirty() bool
ResetDirtyFlags()
}
func NewEditorContext() EditorContext {
return &editorContext{}
}
type editorContext struct {
effectiveCommandSet *CommandSet
commandExecutors []Executor
currentStage Stage
requestPath string
requestHeaders map[string][]string
requestQueries map[string][]string
responseHeaders map[string][]string
requestHeadersDirty bool
responseHeadersDirty bool
}
func (ctx *editorContext) GetEffectiveCommandSet() *CommandSet {
return ctx.effectiveCommandSet
}
func (ctx *editorContext) SetEffectiveCommandSet(cmdSet *CommandSet) {
ctx.effectiveCommandSet = cmdSet
}
func (ctx *editorContext) GetCommandExecutors() []Executor {
return ctx.commandExecutors
}
func (ctx *editorContext) SetCommandExecutors(executors []Executor) {
ctx.commandExecutors = executors
}
func (ctx *editorContext) GetCurrentStage() Stage {
return ctx.currentStage
}
func (ctx *editorContext) SetCurrentStage(stage Stage) {
ctx.currentStage = stage
}
func (ctx *editorContext) GetRequestPath() string {
return ctx.requestPath
}
func (ctx *editorContext) SetRequestPath(path string) {
ctx.requestPath = path
ctx.savePathToHeader()
}
func (ctx *editorContext) GetRequestHeader(key string) []string {
if ctx.requestHeaders == nil {
return nil
}
return ctx.requestHeaders[strings.ToLower(key)]
}
func (ctx *editorContext) GetRequestHeaders() map[string][]string {
return maps.Clone(ctx.requestHeaders)
}
func (ctx *editorContext) SetRequestHeaders(headers map[string][]string) {
ctx.requestHeaders = headers
ctx.loadPathFromHeader()
ctx.requestHeadersDirty = true
}
func (ctx *editorContext) GetRequestQuery(key string) []string {
if ctx.requestQueries == nil {
return nil
}
return ctx.requestQueries[key]
}
func (ctx *editorContext) GetRequestQueries() map[string][]string {
return maps.Clone(ctx.requestQueries)
}
func (ctx *editorContext) SetRequestQueries(queries map[string][]string) {
ctx.requestQueries = queries
ctx.savePathToHeader()
}
func (ctx *editorContext) GetResponseHeader(key string) []string {
if ctx.responseHeaders == nil {
return nil
}
return ctx.responseHeaders[strings.ToLower(key)]
}
func (ctx *editorContext) GetResponseHeaders() map[string][]string {
return maps.Clone(ctx.responseHeaders)
}
func (ctx *editorContext) SetResponseHeaders(headers map[string][]string) {
ctx.responseHeaders = headers
ctx.responseHeadersDirty = true
}
func (ctx *editorContext) GetRefValue(ref *Ref) string {
values := ctx.GetRefValues(ref)
if len(values) == 0 {
return ""
}
return values[0]
}
func (ctx *editorContext) GetRefValues(ref *Ref) []string {
if ref == nil {
return nil
}
switch ref.Type {
case RefTypeRequestHeader:
return ctx.GetRequestHeader(strings.ToLower(ref.Name))
case RefTypeRequestQuery:
return ctx.GetRequestQuery(ref.Name)
case RefTypeResponseHeader:
return ctx.GetResponseHeader(strings.ToLower(ref.Name))
default:
return nil
}
}
func (ctx *editorContext) SetRefValue(ref *Ref, value string) {
if ref == nil {
return
}
ctx.SetRefValues(ref, []string{value})
}
func (ctx *editorContext) SetRefValues(ref *Ref, values []string) {
if ref == nil {
return
}
switch ref.Type {
case RefTypeRequestHeader:
if ctx.requestHeaders == nil {
ctx.requestHeaders = make(map[string][]string)
}
loweredRefName := strings.ToLower(ref.Name)
ctx.requestHeaders[loweredRefName] = values
ctx.requestHeadersDirty = true
if loweredRefName == pathHeader {
ctx.loadPathFromHeader()
}
break
case RefTypeRequestQuery:
if ctx.requestQueries == nil {
ctx.requestQueries = make(map[string][]string)
}
ctx.requestQueries[ref.Name] = values
ctx.savePathToHeader()
break
case RefTypeResponseHeader:
if ctx.responseHeaders == nil {
ctx.responseHeaders = make(map[string][]string)
}
ctx.responseHeaders[strings.ToLower(ref.Name)] = values
ctx.responseHeadersDirty = true
break
}
}
func (ctx *editorContext) DeleteRefValues(ref *Ref) {
if ref == nil {
return
}
switch ref.Type {
case RefTypeRequestHeader:
delete(ctx.requestHeaders, strings.ToLower(ref.Name))
ctx.requestHeadersDirty = true
break
case RefTypeRequestQuery:
delete(ctx.requestQueries, ref.Name)
ctx.savePathToHeader()
break
case RefTypeResponseHeader:
delete(ctx.responseHeaders, strings.ToLower(ref.Name))
ctx.responseHeadersDirty = true
break
}
}
func (ctx *editorContext) IsRequestHeadersDirty() bool {
return ctx.requestHeadersDirty
}
func (ctx *editorContext) IsResponseHeadersDirty() bool {
return ctx.responseHeadersDirty
}
func (ctx *editorContext) ResetDirtyFlags() {
ctx.requestHeadersDirty = false
ctx.responseHeadersDirty = false
}
func (ctx *editorContext) savePathToHeader() {
u, err := url.Parse(ctx.requestPath)
if err != nil {
log.Errorf("failed to build the new path with query strings: %v", err)
return
}
query := url.Values{}
for k, vs := range ctx.requestQueries {
for _, v := range vs {
query.Add(k, v)
}
}
u.RawQuery = query.Encode()
ctx.SetRefValue(&Ref{Type: RefTypeRequestHeader, Name: pathHeader}, u.String())
}
func (ctx *editorContext) loadPathFromHeader() {
paths := ctx.GetRequestHeader(pathHeader)
if len(paths) == 0 || paths[0] == "" {
log.Warn("the request has an empty path")
ctx.requestPath = ""
ctx.requestQueries = make(map[string][]string)
return
}
path := paths[0]
queries := make(map[string][]string)
u, err := url.Parse(path)
if err != nil {
log.Warnf("unable to parse the request path: %s", path)
ctx.requestPath = ""
ctx.requestQueries = make(map[string][]string)
return
}
ctx.requestPath = u.Path
for k, vs := range u.Query() {
queries[k] = vs
}
ctx.requestQueries = queries
}