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

@@ -69,7 +69,7 @@ func (cfg *ConfigBase) Validate() gperr.Error {
// If only stdout is enabled, it returns nil, nil.
func (cfg *ConfigBase) IO() (WriterWithName, error) {
if cfg.Path != "" {
io, err := newFileIO(cfg.Path)
io, err := NewFileIO(cfg.Path)
if err != nil {
return nil, err
}

View File

@@ -26,7 +26,10 @@ var (
openedFilesMu sync.Mutex
)
func newFileIO(path string) (WriterWithName, error) {
// NewFileIO creates a new file writer with cleaned path.
//
// If the file is already opened, it will be returned.
func NewFileIO(path string) (WriterWithName, error) {
openedFilesMu.Lock()
defer openedFilesMu.Unlock()

View File

@@ -31,7 +31,7 @@ func TestConcurrentFileLoggersShareSameAccessLogIO(t *testing.T) {
wg.Add(1)
go func(index int) {
defer wg.Done()
file, err := newFileIO(cfg.Path)
file, err := NewFileIO(cfg.Path)
expect.NoError(t, err)
accessLogIOs[index] = file
}(i)

View File

@@ -61,6 +61,26 @@ func NewLogger(out ...io.Writer) zerolog.Logger {
).Level(level).With().Timestamp().Logger()
}
func NewLoggerWithFixedLevel(level zerolog.Level, out ...io.Writer) zerolog.Logger {
levelStr := level.String()
writer := zerolog.ConsoleWriter{
Out: zerolog.MultiLevelWriter(out...),
TimeFormat: timeFmt,
FormatMessage: func(msgI interface{}) string { // pad spaces for each line
if msgI == nil {
return ""
}
return fmtMessage(msgI.(string))
},
FormatLevel: func(_ any) string {
return levelStr
},
}
return zerolog.New(
writer,
).Level(level).With().Timestamp().Logger()
}
func InitLogger(out ...io.Writer) {
logger = NewLogger(out...)
log.SetOutput(logger)