fix: prevent double-recording when HTTP response cache is enabled

The cache-hit path (serveCachedResponse) recorded on the same HandlerRecorderObject
as the calling handler's deferred Record(), producing two records per request.
Copy ro at HandleHTTP entry so the sniffer's internal recording stays isolated
from the caller's defer. Also zero ro.Time when the sniffer takes over, so the
handler's defer Record() skips the now-redundant connection-level record.

Fixes 10 files across sniffer and all 8 handlers that support sniffed HTTP forwarding.
This commit is contained in:
ginuerzh
2026-07-26 20:11:36 +08:00
parent db3c7a375c
commit 486a77665d
10 changed files with 25 additions and 0 deletions
+1
View File
@@ -232,6 +232,7 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
conn = xnet.NewReadWriteConn(br, conn, conn)
handled, sniffErr := h.handleSniffedProtocol(ctx, conn, ro, log, proto)
if handled {
ro.Time = time.Time{}
return sniffErr
}
}
+1
View File
@@ -228,6 +228,7 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
conn = xnet.NewReadWriteConn(br, conn, conn)
handled, sniffErr := h.handleSniffedProtocol(ctx, conn, ro, log, proto)
if handled {
ro.Time = time.Time{}
return sniffErr
}
}
+1
View File
@@ -80,6 +80,7 @@ func (h *httpHandler) handleConnect(ctx context.Context, conn net.Conn, ro *xrec
if h.md.sniffing {
snifferHandled, err = h.sniffAndHandle(ctx, conn, cc, ro, log)
if snifferHandled {
ro.Time = time.Time{}
return err
}
}
+1
View File
@@ -227,6 +227,7 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han
conn = xnet.NewReadWriteConn(br, conn, conn)
switch proto {
case sniffing.ProtoHTTP:
ro.Time = time.Time{}
return sniffer.HandleHTTP(ctx, "tcp", conn,
sniffing.WithService(h.options.Service),
sniffing.WithDial(dial),
+2
View File
@@ -233,6 +233,7 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network
conn = xnet.NewReadWriteConn(br, conn, conn)
switch proto {
case sniffing.ProtoHTTP:
ro.Time = time.Time{}
return sniffer.HandleHTTP(ctx, "tcp", conn,
sniffing.WithService(h.options.Service),
sniffing.WithDial(dial),
@@ -242,6 +243,7 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network
sniffing.WithLog(log),
)
case sniffing.ProtoTLS:
ro.Time = time.Time{}
return sniffer.HandleTLS(ctx, "tcp", conn,
sniffing.WithService(h.options.Service),
sniffing.WithDial(dial),
+1
View File
@@ -189,6 +189,7 @@ func (h *sniHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.
conn = xnet.NewReadWriteConn(br, conn, conn)
handled, sniffErr := h.handleSniffedProtocol(ctx, conn, ro, log, proto)
if handled {
ro.Time = time.Time{}
return sniffErr
}
+2
View File
@@ -233,6 +233,7 @@ func (h *ssHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.H
conn = xnet.NewReadWriteConn(br, conn, conn)
switch proto {
case sniffing.ProtoHTTP:
ro.Time = time.Time{}
return sniffer.HandleHTTP(ctx, "tcp", conn,
sniffing.WithService(h.options.Service),
sniffing.WithDial(dial),
@@ -242,6 +243,7 @@ func (h *ssHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.H
sniffing.WithLog(log),
)
case sniffing.ProtoTLS:
ro.Time = time.Time{}
return sniffer.HandleTLS(ctx, "tcp", conn,
sniffing.WithService(h.options.Service),
sniffing.WithDial(dial),
+2
View File
@@ -177,6 +177,7 @@ func (h *unixHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler
conn = xnet.NewReadWriteConn(br, conn, conn)
switch proto {
case sniffing.ProtoHTTP:
ro.Time = time.Time{}
return sniffer.HandleHTTP(ctx, "tcp", conn,
sniffing.WithService(h.options.Service),
sniffing.WithDial(dial),
@@ -186,6 +187,7 @@ func (h *unixHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler
sniffing.WithLog(log),
)
case sniffing.ProtoTLS:
ro.Time = time.Time{}
return sniffer.HandleTLS(ctx, "tcp", conn,
sniffing.WithService(h.options.Service),
sniffing.WithDial(dial),
+7
View File
@@ -70,6 +70,13 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
ro := ho.recorderObject
// Copy ro so that all internal recording (cache-hit serveCachedResponse
// and cache-miss httpRoundTrip) happens on a local clone, preventing
// double-record when the caller's defer also records ro.
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
if clientIP := xhttp.GetClientIP(req); clientIP != nil {
clientAddr := &net.TCPAddr{IP: clientIP}
ro.ClientAddr = clientAddr.String()
+7
View File
@@ -52,6 +52,13 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, network string, conn net.Conn,
ro := ho.recorderObject
// Copy ro so that all internal recording (cache-hit serveCachedResponse
// and cache-miss httpRoundTrip) happens on a local clone, preventing
// double-record when the caller's defer also records ro.
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
if clientIP := xhttp.GetClientIP(req); clientIP != nil {
ro.ClientIP = clientIP.String()
ctx = xctx.ContextWithSrcAddr(ctx, &net.TCPAddr{IP: clientIP})