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

@@ -15,13 +15,13 @@ type (
)
const (
CacheKeyQueries = "queries"
CacheKeyCookies = "cookies"
CacheKeyRemoteIP = "remote_ip"
CacheKeyBasicAuth = "basic_auth"
cacheKeyQueries = "queries"
cacheKeyCookies = "cookies"
cacheKeyRemoteIP = "remote_ip"
cacheKeyBasicAuth = "basic_auth"
)
var cachePool = &sync.Pool{
var cachePool = sync.Pool{
New: func() any {
return make(Cache)
},
@@ -41,10 +41,10 @@ func (c Cache) Release() {
// GetQueries returns the queries.
// If r does not have queries, an empty map is returned.
func (c Cache) GetQueries(r *http.Request) url.Values {
v, ok := c[CacheKeyQueries]
v, ok := c[cacheKeyQueries]
if !ok {
v = r.URL.Query()
c[CacheKeyQueries] = v
c[cacheKeyQueries] = v
}
return v.(url.Values)
}
@@ -58,17 +58,17 @@ func (c Cache) UpdateQueries(r *http.Request, update func(url.Values)) {
// GetCookies returns the cookies.
// If r does not have cookies, an empty slice is returned.
func (c Cache) GetCookies(r *http.Request) []*http.Cookie {
v, ok := c[CacheKeyCookies]
v, ok := c[cacheKeyCookies]
if !ok {
v = r.Cookies()
c[CacheKeyCookies] = v
c[cacheKeyCookies] = v
}
return v.([]*http.Cookie)
}
func (c Cache) UpdateCookies(r *http.Request, update UpdateFunc[[]*http.Cookie]) {
cookies := update(c.GetCookies(r))
c[CacheKeyCookies] = cookies
c[cacheKeyCookies] = cookies
r.Header.Del("Cookie")
for _, cookie := range cookies {
r.AddCookie(cookie)
@@ -78,14 +78,14 @@ func (c Cache) UpdateCookies(r *http.Request, update UpdateFunc[[]*http.Cookie])
// GetRemoteIP returns the remote ip address.
// If r.RemoteAddr is not a valid ip address, nil is returned.
func (c Cache) GetRemoteIP(r *http.Request) net.IP {
v, ok := c[CacheKeyRemoteIP]
v, ok := c[cacheKeyRemoteIP]
if !ok {
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
host = r.RemoteAddr
}
v = net.ParseIP(host)
c[CacheKeyRemoteIP] = v
c[cacheKeyRemoteIP] = v
}
return v.(net.IP)
}
@@ -93,14 +93,14 @@ func (c Cache) GetRemoteIP(r *http.Request) net.IP {
// GetBasicAuth returns *Credentials the basic auth username and password.
// If r does not have basic auth, nil is returned.
func (c Cache) GetBasicAuth(r *http.Request) *Credentials {
v, ok := c[CacheKeyBasicAuth]
v, ok := c[cacheKeyBasicAuth]
if !ok {
u, p, ok := r.BasicAuth()
if ok {
v = &Credentials{u, []byte(p)}
c[CacheKeyBasicAuth] = v
c[cacheKeyBasicAuth] = v
} else {
c[CacheKeyBasicAuth] = nil
c[cacheKeyBasicAuth] = nil
return nil
}
}