rproxy: close the connection when a short answer is cut off (closes #318)
check / check (push) Waiting to run
check / check (push) Waiting to run
When SWRP_MAX_RESPONSE_TIME_MS ran out before the last of a short answer was written, the flush failed. If the visitor had stopped short of its announced body, Go's server then read the rest until SWRP_MAX_REQUEST_TIME_MS ran out, keeping the connection open with the answer cut off. finish now flushes once more, and if that fails at its deadline, it ends the reading of the body at once. ReverseProxy ignores the failed flush when the app's answer came in one read, and panics when it was still waiting for more; finish runs after both. Go's server never reuses a connection after a failed write, so it closes it. Model: opus-5-5
This commit was merged in pull request #322.
This commit is contained in:
1 parent
b9cfbc7fcb
commit
eba697af48
1 file changed
+18
-7
@@ -384,8 +384,19 @@ func (rq *request) refuse(status int) {
|
|||||||
func (rq *request) finish(ctx context.Context) {
|
func (rq *request) finish(ctx context.Context) {
|
||||||
rq.stopTimers()
|
rq.stopTimers()
|
||||||
|
|
||||||
if rq.refused.Load() == 0 {
|
// After a cut-off answer, the reading of a body that has not arrived whole
|
||||||
rq.stopPassingOnBody()
|
// 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()
|
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
|
// 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
|
// not arrived whole, and lets Go's server read the rest until deadline. Go's
|
||||||
// SWRP_MAX_REQUEST_TIME_MS runs out. Go's server reads it once the handler
|
// server reads it once the handler returns, but with no deadline at all if
|
||||||
// returns, but with no deadline at all if another read of it is still waiting.
|
// another read of it is still waiting.
|
||||||
func (rq *request) stopPassingOnBody() {
|
func (rq *request) stopPassingOnBody(deadline time.Time) {
|
||||||
if rq.body == nil || rq.body.received.Load() {
|
if rq.body == nil || rq.body.received.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -489,7 +500,7 @@ func (rq *request) stopPassingOnBody() {
|
|||||||
rq.body.stopped = true
|
rq.body.stopped = true
|
||||||
rq.body.mu.Unlock()
|
rq.body.mu.Unlock()
|
||||||
|
|
||||||
rq.stopReadingBody(rq.requestDeadline())
|
rq.stopReadingBody(deadline)
|
||||||
}
|
}
|
||||||
|
|
||||||
// startRequestTimer starts SWRP_MAX_REQUEST_TIME_MS, which runs from the
|
// startRequestTimer starts SWRP_MAX_REQUEST_TIME_MS, which runs from the
|
||||||
|
|||||||
Reference in new issue
Block a user