rproxy: send its own answers at once to a visitor that stops sending its body (closes #303)
check / check (push) Successful in 6m26s
check / check (push) Successful in 6m26s
An answer rproxy gives itself, such as a deny or a refusal, waited while Go's server read up to 256 KiB of a body that had not arrived whole. A visitor that stopped sending got nothing until SWRP_MAX_REQUEST_TIME_MS ran out, and no bytes at all once that was past the 10 seconds the answer has to be sent. send now sets Connection: close in that case, as modifyResponse does for the app's answer; both call closeAfterAnswer. requestTimedOut no longer ends that read of Go's server, which no longer happens, so the time the app's answer started is no longer noted. Model: opus-5-5
This commit was merged in pull request #304.
This commit is contained in:
1 parent
f64752e3b7
commit
e2b27d1c7c
3 files changed
+26
-37
No files matched your search
+15
-31
@@ -83,8 +83,8 @@ type request struct {
|
||||
upgraded *upgradedConn
|
||||
|
||||
// mu guards what follows. The time limits run on goroutines of their own,
|
||||
// and the transport starts and stops them, and notes the times below, from
|
||||
// its own; once timersStopped is set, neither time limit acts any more.
|
||||
// and the transport starts and stops them, and notes requestEnd, from its
|
||||
// own; once timersStopped is set, neither time limit acts any more.
|
||||
mu sync.Mutex
|
||||
timersStopped bool
|
||||
requestTimer *time.Timer
|
||||
@@ -92,8 +92,6 @@ type request struct {
|
||||
// requestEnd is when the app had been sent the whole request, or the answer
|
||||
// started, if that was sooner.
|
||||
requestEnd time.Time
|
||||
// answerStarted is when the first byte of the app's answer arrived.
|
||||
answerStarted time.Time
|
||||
}
|
||||
|
||||
// newRequest starts handling r: it notes the time, works out the visitor from
|
||||
@@ -177,8 +175,7 @@ func (rq *request) forward(ctx context.Context) {
|
||||
|
||||
rq.cancel = cancel
|
||||
ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
|
||||
WroteRequest: rq.wroteRequest,
|
||||
GotFirstResponseByte: rq.gotFirstResponseByte,
|
||||
WroteRequest: rq.wroteRequest,
|
||||
})
|
||||
|
||||
out := rq.in.WithContext(ctx)
|
||||
@@ -273,12 +270,7 @@ func (rq *request) modifyResponse(res *http.Response) error {
|
||||
rq.out.Header()["Content-Type"] = nil
|
||||
}
|
||||
|
||||
// Go's server reads up to 256 KiB of a body left unread before it sends an
|
||||
// answer, and so waits on a visitor that has stopped sending. With this
|
||||
// header it sends the answer at once, and closes the connection after it.
|
||||
if rq.body != nil && !rq.body.received.Load() {
|
||||
rq.out.Header().Set("Connection", "close")
|
||||
}
|
||||
rq.closeAfterAnswer()
|
||||
|
||||
res.Body = &responseBody{body: limitBody(res.Body, maxBytes), rq: rq}
|
||||
rq.out.afterStatus = rq.setResponseDeadline
|
||||
@@ -347,11 +339,22 @@ func (rq *request) send(status int, body []byte) {
|
||||
rq.out.Header().Set("Content-Length", strconv.Itoa(len(body)))
|
||||
}
|
||||
|
||||
rq.closeAfterAnswer()
|
||||
rq.out.WriteHeader(status)
|
||||
_, _ = rq.out.Write(body)
|
||||
_ = rq.rc.Flush()
|
||||
}
|
||||
|
||||
// closeAfterAnswer has Go's server send the answer at once, and close the
|
||||
// connection after it, when the visitor's body has not arrived whole. Without
|
||||
// it, Go's server reads up to 256 KiB of the body left before it sends an
|
||||
// answer, and so waits on a visitor that has stopped sending.
|
||||
func (rq *request) closeAfterAnswer() {
|
||||
if rq.body != nil && !rq.body.received.Load() {
|
||||
rq.out.Header().Set("Connection", "close")
|
||||
}
|
||||
}
|
||||
|
||||
// prepareOwnAnswer readies the connection for an answer rproxy gives itself: the
|
||||
// reading of a body that has not arrived whole ends at deadline, and the answer
|
||||
// has SWRP_MAX_RESPONSE_TIME_MS from now to reach the visitor, even when the app
|
||||
@@ -517,16 +520,6 @@ func (rq *request) requestTimedOut() {
|
||||
}
|
||||
|
||||
if rq.body == nil || !rq.body.waiting.Load() {
|
||||
// Once the app has answered, Go's server can read what is left of a body
|
||||
// under 256 KiB without requestBody.Read, before an answer rproxy gives
|
||||
// itself, so waiting is false while that read waits for the visitor.
|
||||
// That read ends now, before refuse:
|
||||
// should rproxy still answer itself, answer then sets the deadline it
|
||||
// gives its own answers.
|
||||
if !rq.answerStarted.IsZero() {
|
||||
rq.stopReadingBody(time.Now())
|
||||
}
|
||||
|
||||
rq.refuse(http.StatusGatewayTimeout)
|
||||
|
||||
return
|
||||
@@ -539,15 +532,6 @@ func (rq *request) requestTimedOut() {
|
||||
_ = rq.rc.SetReadDeadline(time.Now())
|
||||
}
|
||||
|
||||
// gotFirstResponseByte is called once the first byte of the app's answer has
|
||||
// arrived.
|
||||
func (rq *request) gotFirstResponseByte() {
|
||||
rq.mu.Lock()
|
||||
defer rq.mu.Unlock()
|
||||
|
||||
rq.answerStarted = time.Now()
|
||||
}
|
||||
|
||||
// wroteRequest is called once the app has been sent the whole request:
|
||||
// SWRP_MAX_REQUEST_TIME_MS ends and SWRP_MAX_RESPONSE_TIME_MS starts.
|
||||
func (rq *request) wroteRequest(info httptrace.WroteRequestInfo) {
|
||||
|
||||
Reference in new issue
Block a user