Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c5aef63bc |
No files matched your search
@@ -2355,6 +2355,10 @@ These limits apply per request, in both directions:
|
|||||||
than 10 seconds. The status and headers of the app's answer also have at least
|
than 10 seconds. The status and headers of the app's answer also have at least
|
||||||
10 seconds to be sent.
|
10 seconds to be sent.
|
||||||
|
|
||||||
|
`rproxy` may give its own answer, such as a refusal, before the visitor has sent
|
||||||
|
its whole body. The visitor gets that answer at once, and the connection closes
|
||||||
|
after it.
|
||||||
|
|
||||||
The largest request line and headers is 32 KiB. A kept-open connection is closed
|
The largest request line and headers is 32 KiB. A kept-open connection is closed
|
||||||
after 120 seconds idle. Neither has a setting. traefik, by default, closes an
|
after 120 seconds idle. Neither has a setting. traefik, by default, closes an
|
||||||
unused connection after 90 seconds. So traefik closes first, and never sends a
|
unused connection after 90 seconds. So traefik closes first, and never sends a
|
||||||
|
|||||||
@@ -974,7 +974,7 @@ func TestTheRequestTimeLimitDoesNotCutAnAnswerOnceTheAppHasTheRequest(t *testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAVisitorThatStopsSendingGetsAnAnswerRProxyGivesByTheDeadline(t *testing.T) {
|
func TestAVisitorThatStopsALittleShortGetsAnAnswerRProxyGivesAtOnce(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
@@ -993,14 +993,15 @@ func TestAVisitorThatStopsSendingGetsAnAnswerRProxyGivesByTheDeadline(t *testing
|
|||||||
|
|
||||||
standIn := startDiscriminator(t, deny)
|
standIn := startDiscriminator(t, deny)
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
// The time limit is long enough for a loaded host to read the first part of
|
// The request time limit is longer than the 10 seconds an answer rproxy
|
||||||
// the body before it runs out.
|
// gives itself has to be sent, and than dial's deadline.
|
||||||
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
maxRequestTime: "3000",
|
maxRequestTime: "15000",
|
||||||
|
maxResponseTime: "300",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Go's server waits for a little of the body left before it sends an answer,
|
// The body stops 100 bytes short, less than Go's server reads of a body
|
||||||
// and the time for sending the request ends that wait.
|
// left unread before it answers.
|
||||||
conn := dial(t, addr)
|
conn := dial(t, addr)
|
||||||
send(t, conn, tc.start+announcedLength(questionBodyMaxBytes+100)+
|
send(t, conn, tc.start+announcedLength(questionBodyMaxBytes+100)+
|
||||||
strings.Repeat("x", questionBodyMaxBytes))
|
strings.Repeat("x", questionBodyMaxBytes))
|
||||||
|
|||||||
+14
-30
@@ -83,8 +83,8 @@ type request struct {
|
|||||||
upgraded *upgradedConn
|
upgraded *upgradedConn
|
||||||
|
|
||||||
// mu guards what follows. The time limits run on goroutines of their own,
|
// 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
|
// and the transport starts and stops them, and notes requestEnd, from its
|
||||||
// its own; once timersStopped is set, neither time limit acts any more.
|
// own; once timersStopped is set, neither time limit acts any more.
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
timersStopped bool
|
timersStopped bool
|
||||||
requestTimer *time.Timer
|
requestTimer *time.Timer
|
||||||
@@ -92,8 +92,6 @@ type request struct {
|
|||||||
// requestEnd is when the app had been sent the whole request, or the answer
|
// requestEnd is when the app had been sent the whole request, or the answer
|
||||||
// started, if that was sooner.
|
// started, if that was sooner.
|
||||||
requestEnd time.Time
|
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
|
// newRequest starts handling r: it notes the time, works out the visitor from
|
||||||
@@ -178,7 +176,6 @@ func (rq *request) forward(ctx context.Context) {
|
|||||||
rq.cancel = cancel
|
rq.cancel = cancel
|
||||||
ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
|
ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
|
||||||
WroteRequest: rq.wroteRequest,
|
WroteRequest: rq.wroteRequest,
|
||||||
GotFirstResponseByte: rq.gotFirstResponseByte,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
out := rq.in.WithContext(ctx)
|
out := rq.in.WithContext(ctx)
|
||||||
@@ -273,12 +270,7 @@ func (rq *request) modifyResponse(res *http.Response) error {
|
|||||||
rq.out.Header()["Content-Type"] = nil
|
rq.out.Header()["Content-Type"] = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go's server reads up to 256 KiB of a body left unread before it sends an
|
rq.closeAfterAnswer()
|
||||||
// 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")
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Body = &responseBody{body: limitBody(res.Body, maxBytes), rq: rq}
|
res.Body = &responseBody{body: limitBody(res.Body, maxBytes), rq: rq}
|
||||||
rq.out.afterStatus = rq.setResponseDeadline
|
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.out.Header().Set("Content-Length", strconv.Itoa(len(body)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rq.closeAfterAnswer()
|
||||||
rq.out.WriteHeader(status)
|
rq.out.WriteHeader(status)
|
||||||
_, _ = rq.out.Write(body)
|
_, _ = rq.out.Write(body)
|
||||||
_ = rq.rc.Flush()
|
_ = 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
|
// 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
|
// 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
|
// 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() {
|
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)
|
rq.refuse(http.StatusGatewayTimeout)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -539,15 +532,6 @@ func (rq *request) requestTimedOut() {
|
|||||||
_ = rq.rc.SetReadDeadline(time.Now())
|
_ = 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:
|
// 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.
|
// SWRP_MAX_REQUEST_TIME_MS ends and SWRP_MAX_RESPONSE_TIME_MS starts.
|
||||||
func (rq *request) wroteRequest(info httptrace.WroteRequestInfo) {
|
func (rq *request) wroteRequest(info httptrace.WroteRequestInfo) {
|
||||||
|
|||||||
Reference in new issue
Block a user