diff --git a/internal/rproxy/request.go b/internal/rproxy/request.go index c346dae..170b14e 100644 --- a/internal/rproxy/request.go +++ b/internal/rproxy/request.go @@ -384,8 +384,19 @@ func (rq *request) refuse(status int) { func (rq *request) finish(ctx context.Context) { rq.stopTimers() - if rq.refused.Load() == 0 { - rq.stopPassingOnBody() + // After a cut-off answer, the reading of a body that has not arrived whole + // ends now. Go's server would otherwise keep the connection open while it + // reads the rest. A flush fails if the time to send the answer ran out, and + // every flush after a failed one fails too. ReverseProxy ignores the failure, + // but panics if it was still waiting for more of the app's answer; finish runs + // either way. Before a status, a flush would send a 200; after a 101, + // ReverseProxy has taken over the connection. + switch { + case rq.out.status != 0 && rq.out.status != http.StatusSwitchingProtocols && + errors.Is(rq.rc.Flush(), os.ErrDeadlineExceeded): + rq.stopPassingOnBody(time.Now()) + case rq.refused.Load() == 0: + rq.stopPassingOnBody(rq.requestDeadline()) } requestEnd := rq.endRequest() @@ -473,10 +484,10 @@ func (rq *request) stopReadingBody(deadline time.Time) { } // stopPassingOnBody ends the transport's reading of a visitor's body that has -// not arrived whole, and lets Go's server read the rest until -// SWRP_MAX_REQUEST_TIME_MS runs out. Go's server reads it once the handler -// returns, but with no deadline at all if another read of it is still waiting. -func (rq *request) stopPassingOnBody() { +// not arrived whole, and lets Go's server read the rest until deadline. Go's +// server reads it once the handler returns, but with no deadline at all if +// another read of it is still waiting. +func (rq *request) stopPassingOnBody(deadline time.Time) { if rq.body == nil || rq.body.received.Load() { return } @@ -489,7 +500,7 @@ func (rq *request) stopPassingOnBody() { rq.body.stopped = true rq.body.mu.Unlock() - rq.stopReadingBody(rq.requestDeadline()) + rq.stopReadingBody(deadline) } // startRequestTimer starts SWRP_MAX_REQUEST_TIME_MS, which runs from the