feat: add zero-cost wildcard prefix matching for Proxy and Rewrite routes (by AI)

This commit is contained in:
AI Engineer 2026-05-13 00:43:43 +08:00
parent 2b7e11e7d2
commit 3cde76a6b0
3 changed files with 75 additions and 61 deletions

View File

@ -2,8 +2,6 @@ package service
import ( import (
"path/filepath" "path/filepath"
"regexp"
"strings"
) )
// CertSet SSL 证书配置 // CertSet SSL 证书配置
@ -90,14 +88,7 @@ func ApplyConfig() {
} }
newProxies := make([]*proxyType, 0, len(rules)) newProxies := make([]*proxyType, 0, len(rules))
for _, r := range rules { for _, r := range rules {
p := &proxyType{authLevel: r.AuthLevel, fromPath: r.Path, toApp: r.ToApp, toPath: r.ToPath} newProxies = append(newProxies, parseProxyRule(r.AuthLevel, r.Path, r.ToApp, r.ToPath))
if strings.ContainsRune(r.Path, '(') {
matcher, err := regexp.Compile("^" + r.Path + "$")
if err == nil {
p.matcher = matcher
}
}
newProxies = append(newProxies, p)
} }
fileProxies[host] = newProxies fileProxies[host] = newProxies
rebuildProxiesUnderLock(host) rebuildProxiesUnderLock(host)
@ -109,14 +100,7 @@ func ApplyConfig() {
} }
newRewrites := make([]*rewriteType, 0, len(rules)) newRewrites := make([]*rewriteType, 0, len(rules))
for _, r := range rules { for _, r := range rules {
s := &rewriteType{fromPath: r.Path, toPath: r.ToPath} newRewrites = append(newRewrites, parseRewriteRule(r.Path, r.ToPath))
if strings.ContainsRune(r.Path, '(') {
matcher, err := regexp.Compile("^" + r.Path + "$")
if err == nil {
s.matcher = matcher
}
}
newRewrites = append(newRewrites, s)
} }
fileRewrites[host] = newRewrites fileRewrites[host] = newRewrites
rebuildRewritesUnderLock(host) rebuildRewritesUnderLock(host)

View File

@ -17,16 +17,32 @@ type proxyType struct {
fromPath string fromPath string
toApp string toApp string
toPath string toPath string
hasWildcard bool
prefix string
toPrefix string
} }
func (hc *HostContext) Proxy(authLevel int, path string, toApp, toPath string) *HostContext { func parseProxyRule(authLevel int, path, toApp, toPath string) *proxyType {
p := &proxyType{authLevel: authLevel, fromPath: path, toApp: toApp, toPath: toPath} p := &proxyType{authLevel: authLevel, fromPath: path, toApp: toApp, toPath: toPath}
if strings.Contains(path, "(") { if strings.ContainsRune(path, '(') {
matcher, err := regexp.Compile("^" + path + "$") matcher, err := regexp.Compile("^" + path + "$")
if err == nil { if err == nil {
p.matcher = matcher p.matcher = matcher
} }
} else if strings.HasSuffix(path, "/*") {
p.hasWildcard = true
p.prefix = path[:len(path)-1]
if strings.HasSuffix(toPath, "/*") {
p.toPrefix = toPath[:len(toPath)-1]
} else {
p.toPrefix = toPath
} }
}
return p
}
func (hc *HostContext) Proxy(authLevel int, path string, toApp, toPath string) *HostContext {
p := parseProxyRule(authLevel, path, toApp, toPath)
hostPoliciesLock.Lock() hostPoliciesLock.Lock()
defer hostPoliciesLock.Unlock() defer hostPoliciesLock.Unlock()
@ -71,12 +87,7 @@ func findProxy(request *Request) (int, *string, *string, string) {
} }
for _, pi := range proxies { for _, pi := range proxies {
if pi.matcher == nil { if pi.matcher != nil {
if pi.fromPath == requestPath {
toPath := pi.toPath + queryString
return pi.authLevel, &pi.toApp, &toPath, h
}
} else {
finds := pi.matcher.FindAllStringSubmatch(requestPath, 1) finds := pi.matcher.FindAllStringSubmatch(requestPath, 1)
if len(finds) > 0 { if len(finds) > 0 {
toApp := pi.toApp toApp := pi.toApp
@ -88,6 +99,18 @@ func findProxy(request *Request) (int, *string, *string, string) {
toPath += queryString toPath += queryString
return pi.authLevel, &toApp, &toPath, h return pi.authLevel, &toApp, &toPath, h
} }
} else if pi.hasWildcard {
if strings.HasPrefix(requestPath, pi.prefix) {
suffix := requestPath[len(pi.prefix):]
toPath := pi.toPrefix + suffix + queryString
toApp := pi.toApp
return pi.authLevel, &toApp, &toPath, h
}
} else {
if pi.fromPath == requestPath {
toPath := pi.toPath + queryString
return pi.authLevel, &pi.toApp, &toPath, h
}
} }
} }
} }
@ -184,14 +207,7 @@ type ProxyRule struct {
func ReplaceProxies(host string, rules []ProxyRule) { func ReplaceProxies(host string, rules []ProxyRule) {
newProxies := make([]*proxyType, 0, len(rules)) newProxies := make([]*proxyType, 0, len(rules))
for _, r := range rules { for _, r := range rules {
p := &proxyType{authLevel: r.AuthLevel, fromPath: r.Path, toApp: r.ToApp, toPath: r.ToPath} newProxies = append(newProxies, parseProxyRule(r.AuthLevel, r.Path, r.ToApp, r.ToPath))
if strings.ContainsRune(r.Path, '(') {
matcher, err := regexp.Compile("^" + r.Path + "$")
if err == nil {
p.matcher = matcher
}
}
newProxies = append(newProxies, p)
} }
hostPoliciesLock.Lock() hostPoliciesLock.Lock()

View File

@ -12,17 +12,32 @@ type rewriteType struct {
matcher *regexp.Regexp matcher *regexp.Regexp
fromPath string fromPath string
toPath string toPath string
hasWildcard bool
prefix string
toPrefix string
} }
func (hc *HostContext) Rewrite(path string, toPath string) *HostContext { func parseRewriteRule(fromPath, toPath string) *rewriteType {
s := &rewriteType{fromPath: path, toPath: toPath} s := &rewriteType{fromPath: fromPath, toPath: toPath}
if strings.ContainsRune(fromPath, '(') {
if strings.ContainsRune(path, '(') { matcher, err := regexp.Compile("^" + fromPath + "$")
matcher, err := regexp.Compile("^" + path + "$")
if err == nil { if err == nil {
s.matcher = matcher s.matcher = matcher
} }
} else if strings.HasSuffix(fromPath, "/*") {
s.hasWildcard = true
s.prefix = fromPath[:len(fromPath)-1]
if strings.HasSuffix(toPath, "/*") {
s.toPrefix = toPath[:len(toPath)-1]
} else {
s.toPrefix = toPath
} }
}
return s
}
func (hc *HostContext) Rewrite(path string, toPath string) *HostContext {
s := parseRewriteRule(path, toPath)
hostPoliciesLock.Lock() hostPoliciesLock.Lock()
defer hostPoliciesLock.Unlock() defer hostPoliciesLock.Unlock()
@ -68,12 +83,7 @@ func processRewrite(request *Request, response *Response, logger *log.Logger) bo
found := false found := false
rewriteToPath := "" rewriteToPath := ""
if ri.matcher == nil { if ri.matcher != nil {
if ri.fromPath == requestPath {
rewriteToPath = ri.toPath
found = true
}
} else {
finds := ri.matcher.FindAllStringSubmatch(request.RequestURI, 1) finds := ri.matcher.FindAllStringSubmatch(request.RequestURI, 1)
if len(finds) > 0 { if len(finds) > 0 {
toPath := ri.toPath toPath := ri.toPath
@ -83,6 +93,17 @@ func processRewrite(request *Request, response *Response, logger *log.Logger) bo
rewriteToPath = toPath rewriteToPath = toPath
found = true found = true
} }
} else if ri.hasWildcard {
if strings.HasPrefix(requestPath, ri.prefix) {
suffix := requestPath[len(ri.prefix):]
rewriteToPath = ri.toPrefix + suffix
found = true
}
} else {
if ri.fromPath == requestPath {
rewriteToPath = ri.toPath
found = true
}
} }
if found { if found {
@ -121,14 +142,7 @@ type RewriteRule struct {
func ReplaceRewrites(host string, rules []RewriteRule) { func ReplaceRewrites(host string, rules []RewriteRule) {
newRewrites := make([]*rewriteType, 0, len(rules)) newRewrites := make([]*rewriteType, 0, len(rules))
for _, r := range rules { for _, r := range rules {
s := &rewriteType{fromPath: r.Path, toPath: r.ToPath} newRewrites = append(newRewrites, parseRewriteRule(r.Path, r.ToPath))
if strings.ContainsRune(r.Path, '(') {
matcher, err := regexp.Compile("^" + r.Path + "$")
if err == nil {
s.matcher = matcher
}
}
newRewrites = append(newRewrites, s)
} }
hostPoliciesLock.Lock() hostPoliciesLock.Lock()