perf: optimize wildcard header deletion; test: assert invalid regex returns 500

- refactor Delete logic to iterate headers once, reducing ToLower calls
  from O(patterns * headers) to O(headers)
- rewrite invalid regex test to verify runtime 500 response
This commit is contained in:
wjqserver 2026-04-21 17:20:30 +08:00
parent c0e31c449e
commit 5d9bb3187d
2 changed files with 78 additions and 27 deletions

View file

@ -136,39 +136,64 @@ func (ops *HeaderOps) applyTo(hdr http.Header, repl *reverseProxyReplacer) {
} }
} }
var deleteAll bool
var exactDeletes []string
var suffixPatterns, prefixPatterns, containsPatterns []string
for _, fieldName := range ops.Delete { for _, fieldName := range ops.Delete {
fieldName = strings.ToLower(repl.Replace(fieldName)) fieldName = strings.ToLower(repl.Replace(fieldName))
if fieldName == "*" { if fieldName == "*" {
for k := range hdr { deleteAll = true
hdr.Del(k) break
} }
continue
}
switch { switch {
case strings.HasPrefix(fieldName, "*") && strings.HasSuffix(fieldName, "*"): case strings.HasPrefix(fieldName, "*") && strings.HasSuffix(fieldName, "*"):
pattern := fieldName[1:len(fieldName)-1] containsPatterns = append(containsPatterns, fieldName[1:len(fieldName)-1])
for k := range hdr {
if strings.Contains(strings.ToLower(k), pattern) {
hdr.Del(k)
}
}
case strings.HasPrefix(fieldName, "*"): case strings.HasPrefix(fieldName, "*"):
suffix := fieldName[1:] suffixPatterns = append(suffixPatterns, fieldName[1:])
for k := range hdr {
if strings.HasSuffix(strings.ToLower(k), suffix) {
hdr.Del(k)
}
}
case strings.HasSuffix(fieldName, "*"): case strings.HasSuffix(fieldName, "*"):
prefix := fieldName[:len(fieldName)-1] prefixPatterns = append(prefixPatterns, fieldName[:len(fieldName)-1])
default:
exactDeletes = append(exactDeletes, fieldName)
}
}
if deleteAll {
for k := range hdr { for k := range hdr {
if strings.HasPrefix(strings.ToLower(k), prefix) {
hdr.Del(k) hdr.Del(k)
} }
} else if len(exactDeletes) > 0 || len(suffixPatterns) > 0 || len(prefixPatterns) > 0 || len(containsPatterns) > 0 {
toDelete := make([]string, 0, len(exactDeletes))
for k := range hdr {
kl := strings.ToLower(k)
for _, d := range exactDeletes {
if kl == d {
toDelete = append(toDelete, k)
goto skip
} }
default: }
hdr.Del(fieldName) for _, p := range containsPatterns {
if strings.Contains(kl, p) {
toDelete = append(toDelete, k)
goto skip
}
}
for _, p := range suffixPatterns {
if strings.HasSuffix(kl, p) {
toDelete = append(toDelete, k)
goto skip
}
}
for _, p := range prefixPatterns {
if strings.HasPrefix(kl, p) {
toDelete = append(toDelete, k)
goto skip
}
}
skip:
}
for _, k := range toDelete {
hdr.Del(k)
} }
} }

View file

@ -193,15 +193,41 @@ func TestReverseProxyHeaderOpsReplaceResponse(t *testing.T) {
} }
func TestReverseProxyHeaderOpsProvisionInvalidRegexp(t *testing.T) { func TestReverseProxyHeaderOpsProvisionInvalidRegexp(t *testing.T) {
_ = New() backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ReverseProxy(ReverseProxyConfig{ w.WriteHeader(http.StatusOK)
Target: mustParseURL(t, "http://example.com"), _, _ = w.Write([]byte("ok"))
}))
defer backend.Close()
target, err := url.Parse(backend.URL)
if err != nil {
t.Fatalf("parse target: %v", err)
}
engine := New()
engine.GET("/test", ReverseProxy(ReverseProxyConfig{
Target: target,
RequestHeaders: &HeaderOps{ RequestHeaders: &HeaderOps{
Replace: map[string][]Replacement{ Replace: map[string][]Replacement{
"X-Test": {{SearchRegexp: "[invalid"}}, "X-Test": {{SearchRegexp: "[invalid"}},
}, },
}, },
}) }))
proxy := httptest.NewServer(engine)
defer proxy.Close()
req, _ := http.NewRequest(http.MethodGet, proxy.URL+"/test", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
_, _ = io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusInternalServerError {
t.Errorf("expected status 500, got %d", resp.StatusCode)
}
} }
func TestReplacementApply(t *testing.T) { func TestReplacementApply(t *testing.T) {