rproxy: close the connection when a short answer is cut off (closes #318) #322
@@ -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,82 @@ func TestAVisitorThatStopsALittleShortIsReadUntilTheRequestTimeLimit(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestAVisitorThatStopsALittleShortHasTheConnectionClosedWhenTheAnswerIsCutOff(
|
||||
t *testing.T,
|
||||
) {
|
||||
t.Parallel()
|
||||
|
||||
const limit = 300 * time.Millisecond
|
||||
|
||||
// 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.
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
parts []string
|
||||
}{
|
||||
{"with the body in one part", []string{greeting}},
|
||||
{"with the body in two parts", []string{greeting[:5], 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)))
|
||||
|
||||
_ = rc.Flush()
|
||||
|
||||
<-headersRead
|
||||
time.Sleep(limit)
|
||||
|
||||
for _, part := range tc.parts {
|
||||
_, _ = 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",
|
||||
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()
|
||||
|
||||
|
||||
Reference in new issue
Block a user