diff --git a/internal/rproxy/request.go b/internal/rproxy/request.go index 170b14e..f2b2490 100644 --- a/internal/rproxy/request.go +++ b/internal/rproxy/request.go @@ -272,6 +272,10 @@ func (rq *request) modifyResponse(res *http.Response) error { rq.closeAfterAnswer() + // An answer that arrives before the app has been sent the whole request ends + // the request, and SWRP_MAX_RESPONSE_TIME_MS starts. + rq.startResponseTimer(requestEnd) + res.Body = &responseBody{body: limitBody(res.Body, maxBytes), rq: rq} rq.out.afterStatus = rq.setResponseDeadline @@ -386,14 +390,18 @@ func (rq *request) finish(ctx context.Context) { // 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. + // reads the rest. modifyResponse sets afterStatus only when it passes the + // app's answer on. Any refusal after that has cut the answer off, since a + // refusal ends the request to the app. Any answer is cut off when the time to + // send it runs out: a flush then fails, 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): + case rq.out.afterStatus != nil && rq.refused.Load() != 0, + 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()) @@ -553,19 +561,29 @@ func (rq *request) wroteRequest(info httptrace.WroteRequestInfo) { requestEnd := rq.endRequest() rq.mu.Lock() - defer rq.mu.Unlock() + stopTimer(rq.requestTimer) + rq.mu.Unlock() - if rq.timersStopped { + rq.startResponseTimer(requestEnd) +} + +// startResponseTimer starts SWRP_MAX_RESPONSE_TIME_MS, which runs from +// requestEnd, unless it has started already. +func (rq *request) startResponseTimer(requestEnd time.Time) { + limit := rq.h.config.MaxResponseTime + if limit == 0 { return } - stopTimer(rq.requestTimer) + rq.mu.Lock() + defer rq.mu.Unlock() - limit := rq.h.config.MaxResponseTime - if limit > 0 { - rq.responseTimer = time.AfterFunc(time.Until(requestEnd.Add(limit)), - rq.responseTimedOut) + if rq.timersStopped || rq.responseTimer != nil { + return } + + rq.responseTimer = time.AfterFunc(time.Until(requestEnd.Add(limit)), + rq.responseTimedOut) } // responseTimedOut is called when SWRP_MAX_RESPONSE_TIME_MS runs out before the