Compare commits
2
Commits
cd30f0c719
...
35446f8ad4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35446f8ad4 | ||
|
|
11ae6f679e |
No files matched your search
@@ -12,7 +12,6 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -918,49 +917,48 @@ func TestAVisitorThatStopsALittleShortHasTheConnectionClosedWhenTheAnswerIsCutOf
|
||||
) {
|
||||
t.Parallel()
|
||||
|
||||
const limit = 300 * time.Millisecond
|
||||
// A busy host passes on a part sent at once well within this limit.
|
||||
const limit = 2 * time.Second
|
||||
|
||||
// With two parts, rproxy fails to send the first and then waits for the
|
||||
// second, which the app sends only once rproxy has ended the request to it.
|
||||
// The app sends the parts in atOnce with its status and headers. It sends the
|
||||
// parts in late only once rproxy has ended the request to it, which rproxy
|
||||
// does when it cuts off the answer, so that those never reach the visitor.
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
parts []string
|
||||
name string
|
||||
atOnce, late []string
|
||||
}{
|
||||
{"with the body in one part", []string{greeting}},
|
||||
{"with the body in two parts", []string{greeting[:5], greeting[5:]}},
|
||||
{"with the body in one part", nil, []string{greeting}},
|
||||
{"with the body in two parts", nil, []string{greeting[:5], greeting[5:]}},
|
||||
{
|
||||
"with the first part of the body at once",
|
||||
[]string{greeting[:5]}, []string{greeting[5:]},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
headersRead := make(chan struct{})
|
||||
// letAppSendBody also runs when the test ends, as the app's server waits
|
||||
// for the app.
|
||||
letAppSendBody := sync.OnceFunc(func() { close(headersRead) })
|
||||
standIn := startWAF(t, allow)
|
||||
// The app sends its body once the visitor has the status and headers and
|
||||
// the response time limit has run out since, so that rproxy cannot send
|
||||
// it. After each part, it reads the request's body, which ends when rproxy
|
||||
// ends the request to it.
|
||||
app := startApp(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
rc := http.NewResponseController(w)
|
||||
_ = rc.EnableFullDuplex()
|
||||
|
||||
w.Header().Set(contentLengthHeader, strconv.Itoa(len(greeting)))
|
||||
|
||||
for _, part := range tc.atOnce {
|
||||
_, _ = io.WriteString(w, part)
|
||||
}
|
||||
|
||||
_ = rc.Flush()
|
||||
|
||||
<-headersRead
|
||||
time.Sleep(limit)
|
||||
// The request's body ends when rproxy ends the request to the app.
|
||||
_, _ = io.Copy(io.Discard, r.Body)
|
||||
|
||||
for _, part := range tc.parts {
|
||||
for _, part := range tc.late {
|
||||
_, _ = io.WriteString(w, part)
|
||||
_ = rc.Flush()
|
||||
_, _ = io.Copy(io.Discard, r.Body)
|
||||
}
|
||||
})
|
||||
|
||||
t.Cleanup(letAppSendBody)
|
||||
|
||||
// The request time limit is far longer than dial's deadline.
|
||||
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||
maxRequestTime: "120000",
|
||||
@@ -975,15 +973,14 @@ func TestAVisitorThatStopsALittleShortHasTheConnectionClosedWhenTheAnswerIsCutOf
|
||||
t.Fatalf("read the answer: %v", err)
|
||||
}
|
||||
|
||||
letAppSendBody()
|
||||
|
||||
// If the connection stays open, dial's deadline ends this read with a
|
||||
// timeout.
|
||||
body, err := io.ReadAll(res.Body)
|
||||
_ = res.Body.Close()
|
||||
|
||||
if len(body) != 0 || !errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
t.Errorf("read %q (%v), want the connection closed", body, err)
|
||||
want := strings.Join(tc.atOnce, "")
|
||||
if string(body) != want || !errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
t.Errorf("read %q (%v), want %q and the connection closed", body, err, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+27
-10
@@ -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,17 @@ 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
|
||||
// reads the rest. The app's answer is cut off by a refusal once it is passed
|
||||
// on, as afterStatus shows, 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 +560,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
|
||||
|
||||
Reference in new issue
Block a user