diff --git a/internal/rproxy/limits_test.go b/internal/rproxy/limits_test.go index 685f05b..3f99abb 100644 --- a/internal/rproxy/limits_test.go +++ b/internal/rproxy/limits_test.go @@ -12,6 +12,7 @@ import ( "os" "strconv" "strings" + "sync" "sync/atomic" "testing" "time" @@ -807,8 +808,10 @@ func TestAVisitorThatStopsSendingAfterTheAppHasAnsweredGetsTheAnswer(t *testing. app := startApp(t, func(w http.ResponseWriter, _ *http.Request) { _, _ = io.WriteString(w, greeting) }) + // The request time limit is far longer than dial's deadline, so that a busy + // host cannot use it up before the app's answer arrives. addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ - maxRequestTime: "3000", + maxRequestTime: "120000", }) // Far more announced than sent, so that Go's server answers for the app @@ -869,11 +872,11 @@ func TestAVisitorThatStopsALittleShortGetsTheAppsAnswerAtOnce(t *testing.T) { standIn := startWAF(t, allow) app := startApp(t, answerBeforeTheBody) - // The request time limit is longer than the 10 seconds the status and headers - // of an answer have to be sent, and than dial's deadline. + // Both time limits are far longer than dial's deadline, so that the answer + // has to come before either runs out, and a busy host cannot use them up. addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ - maxRequestTime: "15000", - maxResponseTime: "300", + maxRequestTime: "120000", + maxResponseTime: "60000", }) conn := dial(t, addr) @@ -910,6 +913,62 @@ func TestAVisitorThatStopsALittleShortIsReadUntilTheRequestTimeLimit(t *testing. } } +func TestAVisitorThatStopsALittleShortHasTheConnectionClosedWhenTheAnswerIsCutOff( + t *testing.T, +) { + t.Parallel() + + const limit = 300 * time.Millisecond + + 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. + app := startApp(t, func(w http.ResponseWriter, r *http.Request) { + rc := http.NewResponseController(w) + _ = rc.EnableFullDuplex() + + w.Header().Set(contentLengthHeader, strconv.Itoa(len(greeting))) + _ = rc.Flush() + + <-headersRead + time.Sleep(limit) + + _, _ = io.WriteString(w, greeting) + _ = 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", + maxResponseTime: strconv.FormatInt(limit.Milliseconds(), 10), + }) + + conn := dial(t, addr) + sendAllButALittle(t, conn) + + res, err := http.ReadResponse(bufio.NewReader(conn), nil) + if err != nil { + 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) + } +} + func TestAVisitorThatStopsSendingWhileTheAppWorksGetsNoAnswer(t *testing.T) { t.Parallel()