rproxy: close the connection when a short answer is cut off (closes #318) #322
@@ -12,6 +12,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -807,8 +808,10 @@ func TestAVisitorThatStopsSendingAfterTheAppHasAnsweredGetsTheAnswer(t *testing.
|
|||||||
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
|
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
|
||||||
_, _ = io.WriteString(w, greeting)
|
_, _ = 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{
|
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
|
// 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)
|
standIn := startWAF(t, allow)
|
||||||
app := startApp(t, answerBeforeTheBody)
|
app := startApp(t, answerBeforeTheBody)
|
||||||
// The request time limit is longer than the 10 seconds the status and headers
|
// Both time limits are far longer than dial's deadline, so that the answer
|
||||||
// of an answer have to be sent, and than dial's deadline.
|
// 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{
|
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
maxRequestTime: "15000",
|
maxRequestTime: "120000",
|
||||||
maxResponseTime: "300",
|
maxResponseTime: "60000",
|
||||||
})
|
})
|
||||||
|
|
||||||
conn := dial(t, addr)
|
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) {
|
func TestAVisitorThatStopsSendingWhileTheAppWorksGetsNoAnswer(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user