Skip to content

Commit 1e51de8

Browse files
authored
Merge pull request Wei-Shaw#937 from lxohi/fix/anthropic-stream-keepalive
fix: 为 Anthropic Messages API 流式转发添加下游 keepalive ping
2 parents 30995b5 + 6e90ec6 commit 1e51de8

1 file changed

Lines changed: 33 additions & 0 deletions

File tree

backend/internal/service/gateway_service.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5998,6 +5998,22 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http
59985998
intervalCh = intervalTicker.C
59995999
}
60006000

6001+
// 下游 keepalive:防止代理/Cloudflare Tunnel 因连接空闲而断开
6002+
keepaliveInterval := time.Duration(0)
6003+
if s.cfg != nil && s.cfg.Gateway.StreamKeepaliveInterval > 0 {
6004+
keepaliveInterval = time.Duration(s.cfg.Gateway.StreamKeepaliveInterval) * time.Second
6005+
}
6006+
var keepaliveTicker *time.Ticker
6007+
if keepaliveInterval > 0 {
6008+
keepaliveTicker = time.NewTicker(keepaliveInterval)
6009+
defer keepaliveTicker.Stop()
6010+
}
6011+
var keepaliveCh <-chan time.Time
6012+
if keepaliveTicker != nil {
6013+
keepaliveCh = keepaliveTicker.C
6014+
}
6015+
lastDataAt := time.Now()
6016+
60016017
// 仅发送一次错误事件,避免多次写入导致协议混乱(写失败时尽力通知客户端)
60026018
errorEventSent := false
60036019
sendErrorEvent := func(reason string) {
@@ -6187,6 +6203,7 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http
61876203
break
61886204
}
61896205
flusher.Flush()
6206+
lastDataAt = time.Now()
61906207
}
61916208
if data != "" {
61926209
if firstTokenMs == nil && data != "[DONE]" {
@@ -6220,6 +6237,22 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http
62206237
}
62216238
sendErrorEvent("stream_timeout")
62226239
return &streamingResult{usage: usage, firstTokenMs: firstTokenMs}, fmt.Errorf("stream data interval timeout")
6240+
6241+
case <-keepaliveCh:
6242+
if clientDisconnected {
6243+
continue
6244+
}
6245+
if time.Since(lastDataAt) < keepaliveInterval {
6246+
continue
6247+
}
6248+
// SSE ping 事件:Anthropic 原生格式,客户端会正确处理,
6249+
// 同时保持连接活跃防止 Cloudflare Tunnel 等代理断开
6250+
if _, werr := fmt.Fprint(w, "event: ping\ndata: {\"type\": \"ping\"}\n\n"); werr != nil {
6251+
clientDisconnected = true
6252+
logger.LegacyPrintf("service.gateway", "Client disconnected during keepalive ping, continuing to drain upstream for billing")
6253+
continue
6254+
}
6255+
flusher.Flush()
62236256
}
62246257
}
62256258

0 commit comments

Comments
 (0)