@@ -11,6 +11,7 @@ import (
1111 "io"
1212 "net/http"
1313 "strings"
14+ "sync"
1415 "time"
1516 "unicode/utf8"
1617
@@ -250,15 +251,16 @@ func (f *Forwarder) Forward(w http.ResponseWriter, r *http.Request, body []byte,
250251 }
251252 translate .ConfigureRequestHeaders (req .Header , targetFormat , exchange .Translated ())
252253
253- // 超时覆盖建立连接到首个响应正文;正文开始后由回调停止计时器。
254+ // 计时器覆盖建立连接到响应正文结束;收到任意上游字节会重置,
255+ // 因此正常持续输出的流不会被限制,卡住的流会及时释放渠道占用。
254256 ctx , cancel := context .WithCancelCause (r .Context ())
255- firstByteTimer := time . AfterFunc (f .firstByteTimeout (), func () { cancel ( errFirstResponseTimeout ) } )
257+ watchdog := newResponseWatchdog (f .firstByteTimeout (), cancel )
256258 req = req .WithContext (ctx )
257259 client := & http.Client {Timeout : 0 , Transport : candidate .NewTransport ()}
258260 start := time .Now ()
259261 resp , err := client .Do (req )
260262 if err != nil {
261- firstByteTimer . Stop ()
263+ watchdog . stop ()
262264 cause := context .Cause (ctx )
263265 cancel (nil )
264266 release ()
@@ -276,11 +278,12 @@ func (f *Forwarder) Forward(w http.ResponseWriter, r *http.Request, body []byte,
276278 lastErr = err
277279 continue
278280 }
281+ resp .Body = watchdogReadCloser {ReadCloser : resp .Body , touch : watchdog .touch }
279282
280283 // 400/404 需要先读取正文,以区分“不支持模型”和普通客户端参数错误。
281284 if resp .StatusCode == http .StatusBadRequest || resp .StatusCode == http .StatusNotFound {
282285 payload , readErr := readLimitedBody (resp .Body , 2 << 20 )
283- firstByteTimer . Stop ()
286+ watchdog . stop ()
284287 cause := context .Cause (ctx )
285288 cancel (nil )
286289 resp .Body .Close ()
@@ -335,7 +338,7 @@ func (f *Forwarder) Forward(w http.ResponseWriter, r *http.Request, body []byte,
335338 // 认证、限流及 5xx 属于渠道失败:记录熔断反馈后尝试下一个上游。
336339 if upstream .IsFailureStatus (resp .StatusCode ) {
337340 payload , _ := readLimitedBody (resp .Body , 64 << 10 )
338- firstByteTimer . Stop ()
341+ watchdog . stop ()
339342 resp .Body .Close ()
340343 cancel (nil )
341344 latency := time .Since (start ).Milliseconds ()
@@ -349,8 +352,8 @@ func (f *Forwarder) Forward(w http.ResponseWriter, r *http.Request, body []byte,
349352 }
350353
351354 // relayResult.committed 表示响应是否已写出;只有未写出时才能安全换源。
352- result := relayTranslatedResponse (r .Context (), w , resp , start , func () { firstByteTimer . Stop () } , exchange )
353- firstByteTimer . Stop ()
355+ result := relayTranslatedResponse (r .Context (), w , resp , start , nil , exchange )
356+ watchdog . stop ()
354357 cause := context .Cause (ctx )
355358 cancel (nil )
356359 release ()
@@ -498,6 +501,83 @@ var errEmptyResponse = errors.New("upstream returned an empty response")
498501var errErrorPayload = errors .New ("upstream returned an error payload with a successful status" )
499502var errFirstResponseTimeout = errors .New ("upstream first response timeout" )
500503
504+ // responseWatchdog cancels an upstream attempt when no response bytes arrive
505+ // within the configured interval. It is reset by every successful body read,
506+ // so a healthy long-running stream remains allowed to continue.
507+ type responseWatchdog struct {
508+ timeout time.Duration
509+ cancel context.CancelCauseFunc
510+ activity chan struct {}
511+ done chan struct {}
512+ stopped chan struct {}
513+ once sync.Once
514+ }
515+
516+ func newResponseWatchdog (timeout time.Duration , cancel context.CancelCauseFunc ) * responseWatchdog {
517+ w := & responseWatchdog {
518+ timeout : timeout , cancel : cancel ,
519+ activity : make (chan struct {}, 1 ), done : make (chan struct {}), stopped : make (chan struct {}),
520+ }
521+ go w .run ()
522+ return w
523+ }
524+
525+ func (w * responseWatchdog ) run () {
526+ timer := time .NewTimer (w .timeout )
527+ defer func () {
528+ if ! timer .Stop () {
529+ select {
530+ case <- timer .C :
531+ default :
532+ }
533+ }
534+ close (w .stopped )
535+ }()
536+ for {
537+ select {
538+ case <- timer .C :
539+ w .cancel (errFirstResponseTimeout )
540+ return
541+ case <- w .activity :
542+ if ! timer .Stop () {
543+ select {
544+ case <- timer .C :
545+ default :
546+ }
547+ }
548+ timer .Reset (w .timeout )
549+ case <- w .done :
550+ return
551+ }
552+ }
553+ }
554+
555+ func (w * responseWatchdog ) touch () {
556+ select {
557+ case w .activity <- struct {}{}:
558+ default :
559+ }
560+ }
561+
562+ func (w * responseWatchdog ) stop () {
563+ w .once .Do (func () { close (w .done ) })
564+ <- w .stopped
565+ }
566+
567+ // watchdogReadCloser reports body activity without changing the body API.
568+ type watchdogReadCloser struct {
569+ io.ReadCloser
570+ touch func ()
571+ }
572+
573+ func (r watchdogReadCloser ) Read (p []byte ) (int , error ) {
574+ n , err := r .ReadCloser .Read (p )
575+ if n > 0 && r .touch != nil {
576+ r .touch ()
577+ }
578+ return n , err
579+ }
580+
501581type relaySource int
502582
503583const (
0 commit comments