feat(rules): add post-request rules system with response manipulation (#160)

* Add comprehensive post-request rules support for response phase
* Enable response body, status, and header manipulation via set commands
* Refactor command handlers to support both request and response phases
* Implement response modifier system for post-request template execution
* Support response-based rule matching with status and header checks
* Add comprehensive benchmarks for matcher performance
* Refactor authentication and proxying commands for unified error handling
* Support negated conditions with !
* Enhance error handling, error formatting and validation
* Routes: add `rule_file` field with rule preset support
* Environment variable substitution: now supports variables without `GODOXY_` prefix

* new conditions:
  * `on resp_header <key> [<value>]`
  * `on status <status>`
* new commands:
  * `require_auth`
  * `set resp_header <key> <template>`
  * `set resp_body <template>`
  * `set status <code>`
  * `log <level> <path> <template>`
  * `notify <level> <provider> <title_template> <body_template>`
This commit is contained in:
Yuzerion
2025-10-14 23:53:06 +08:00
committed by GitHub
parent 19968834d2
commit 53f3397b7a
41 changed files with 4425 additions and 528 deletions

View File

@@ -2,7 +2,11 @@ package route
import (
"context"
"errors"
"fmt"
"net/url"
"os"
"reflect"
"runtime"
"strings"
"sync"
@@ -17,6 +21,7 @@ import (
netutils "github.com/yusing/godoxy/internal/net"
nettypes "github.com/yusing/godoxy/internal/net/types"
"github.com/yusing/godoxy/internal/proxmox"
"github.com/yusing/godoxy/internal/serialization"
"github.com/yusing/godoxy/internal/types"
gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings"
@@ -25,6 +30,7 @@ import (
"github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/logging/accesslog"
"github.com/yusing/godoxy/internal/route/rules"
rulepresets "github.com/yusing/godoxy/internal/route/rules/presets"
route "github.com/yusing/godoxy/internal/route/types"
"github.com/yusing/godoxy/internal/utils"
)
@@ -41,7 +47,8 @@ type (
route.HTTPConfig
PathPatterns []string `json:"path_patterns,omitempty" extensions:"x-nullable"`
Rules rules.Rules `json:"rules,omitempty" validate:"omitempty,unique=Name" extension:"x-nullable"`
Rules rules.Rules `json:"rules,omitempty" extension:"x-nullable"`
RuleFile string `json:"rule_file,omitempty" extensions:"x-nullable"`
HealthCheck *types.HealthCheckConfig `json:"healthcheck"`
LoadBalance *types.LoadBalancerConfig `json:"load_balance,omitempty" extensions:"x-nullable"`
Middlewares map[string]types.LabelMap `json:"middlewares,omitempty" extensions:"x-nullable"`
@@ -212,7 +219,10 @@ func (r *Route) Validate() gperr.Error {
}
}
errs := gperr.NewBuilder("entry validation failed")
var errs gperr.Builder
if err := r.validateRules(); err != nil {
errs.Add(err)
}
var impl types.Route
var err gperr.Error
@@ -267,6 +277,39 @@ func (r *Route) Validate() gperr.Error {
return nil
}
func (r *Route) validateRules() error {
if r.RuleFile != "" && len(r.Rules) > 0 {
return errors.New("`rule_file` and `rules` cannot be used together")
} else if r.RuleFile != "" {
src, err := url.Parse(r.RuleFile)
if err != nil {
return fmt.Errorf("failed to parse rule file url %q: %w", r.RuleFile, err)
}
switch src.Scheme {
case "embed": // embed://<preset_file_name>
rules, ok := rulepresets.GetRulePreset(src.Host)
if !ok {
return fmt.Errorf("rule preset %q not found", src.Host)
} else {
r.Rules = rules
}
case "file", "":
content, err := os.ReadFile(src.Path)
if err != nil {
return fmt.Errorf("failed to read rule file %q: %w", src.Path, err)
} else {
_, err = serialization.ConvertString(string(content), reflect.ValueOf(&r.Rules))
if err != nil {
return fmt.Errorf("failed to unmarshal rule file %q: %w", src.Path, err)
}
}
default:
return fmt.Errorf("unsupported rule file scheme %q", src.Scheme)
}
}
return nil
}
func (r *Route) Impl() types.Route {
return r.impl
}