fix: tighten reverse proxy safety handling

Avoid HTTP error writes after hijacking upgraded connections, document ModifyResponse constraints for 101 responses, and normalize forwarded query strings consistently to reduce parsing ambiguity across proxy chains.
This commit is contained in:
wjqserver 2026-03-29 01:39:09 +08:00
parent 1946216c0e
commit 6d89b8674f
3 changed files with 13 additions and 5 deletions

View file

@ -168,10 +168,17 @@ r.ANY("/api/*path", touka.ReverseProxy(touka.ReverseProxyConfig{
在后端返回响应后、写回客户端前,对响应做额外处理。 在后端返回响应后、写回客户端前,对响应做额外处理。
注意:`ModifyResponse` 也会作用于 `101 Switching Protocols` 响应。
如果该代理路由需要转发 WebSocket 或其他 Upgrade 流量,请不要在这里消费、完全缓冲,或替换 `resp.Body` 为只读对象;后续升级流程仍然要求它保留 `io.ReadWriteCloser` 能力。
更稳妥的做法是对 `101` 响应直接跳过这类处理。
```go ```go
r.ANY("/api/*path", touka.ReverseProxy(touka.ReverseProxyConfig{ r.ANY("/api/*path", touka.ReverseProxy(touka.ReverseProxyConfig{
Target: target, Target: target,
ModifyResponse: func(resp *http.Response) error { ModifyResponse: func(resp *http.Response) error {
if resp.StatusCode == http.StatusSwitchingProtocols {
return nil
}
resp.Header.Set("X-Proxy", "touka") resp.Header.Set("X-Proxy", "touka")
return nil return nil
}, },

View file

@ -472,6 +472,10 @@ func (p *reverseProxyHandler) handleError(c *Context, err error) {
return return
} }
c.AddError(err) c.AddError(err)
if c.Writer.IsHijacked() {
p.logf(c, "reverse proxy error after hijack: %v", err)
return
}
if p.config.ErrorHandler != nil { if p.config.ErrorHandler != nil {
p.config.ErrorHandler(c.Writer, c.Request, err) p.config.ErrorHandler(c.Writer, c.Request, err)
if c.Writer.Written() || c.Writer.IsHijacked() { if c.Writer.Written() || c.Writer.IsHijacked() {
@ -906,10 +910,7 @@ func cleanReverseProxyQueryParams(rawQuery string) string {
if rawQuery == "" { if rawQuery == "" {
return "" return ""
} }
values, err := url.ParseQuery(rawQuery) values, _ := url.ParseQuery(rawQuery)
if err == nil {
return rawQuery
}
return values.Encode() return values.Encode()
} }

View file

@ -74,7 +74,7 @@ func TestReverseProxyForwardingAndHopHeaders(t *testing.T) {
Via: "proxy.test", Via: "proxy.test",
})) }))
req := httptest.NewRequest(http.MethodGet, "http://client.example/api/ping?q=2", nil) req := httptest.NewRequest(http.MethodGet, "http://client.example/api/ping?bad=1;smuggle=2&q=2", nil)
req.Host = "client.example" req.Host = "client.example"
req.RemoteAddr = "198.51.100.10:4567" req.RemoteAddr = "198.51.100.10:4567"
req.Header.Set("Connection", "X-Remove-Me") req.Header.Set("Connection", "X-Remove-Me")