rproxy: read the rest of a Connection: close visitor's body after an early answer (closes #373)
check / check (push) Waiting to run
check / check (push) Waiting to run
Go's server reads up to 256 KiB of a body left unread once the handler returns, but not a body with a declared length from a visitor that sent Connection: close. It closes that connection with the rest unread, and the visitor, still sending, gets a reset. rproxy now reads that rest itself, until the deadline already set on the connection, through a MaxBytesReader made with Go's own ResponseWriter. With more than 256 KiB left, that has Go's server tell the visitor it sends no more and wait 500 ms before it closes. rproxy reads the rest only after an answer with a Content-Length, because Go's server ends any other answer only once the handler returns. Model: opus-5-5
This commit is contained in:
1 parent
101cc0aba5
commit
3f8dc32927
3 files changed
+172
-7
No files matched your search
@@ -38,6 +38,10 @@ const plainText = "text/plain; charset=utf-8"
|
||||
// and the visitor then gets a closed connection and no answer.
|
||||
const minSendTime = 10 * time.Second
|
||||
|
||||
// maxBodyReadAfterAnswer is the most of a body left unread that Go's server
|
||||
// reads once the handler returns, for a visitor without Connection: close.
|
||||
const maxBodyReadAfterAnswer = 256 << 10
|
||||
|
||||
// errResponseTooLate refuses an app's answer that arrives once
|
||||
// SWW_RPROXY_MAX_RESPONSE_TIME_MS has run out.
|
||||
var errResponseTooLate = errors.New(
|
||||
@@ -338,8 +342,8 @@ func (rq *request) answer(status int) {
|
||||
}
|
||||
|
||||
// A visitor found too slow is read no more; any other may go on sending until
|
||||
// its time is up, so that Go's server can read the rest of the body and end
|
||||
// the request cleanly.
|
||||
// its time is up, so that the rest of the body is read and the request ends
|
||||
// cleanly: by Go's server, or by readRestOfBody.
|
||||
deadline := rq.requestDeadline()
|
||||
if status == http.StatusRequestTimeout {
|
||||
deadline = time.Now()
|
||||
@@ -523,10 +527,35 @@ func (rq *request) stopReadingBody(deadline time.Time) {
|
||||
_ = rq.rc.SetReadDeadline(deadline)
|
||||
}
|
||||
|
||||
// readRestOfBody reads up to 256 KiB of the rest of the body once the visitor
|
||||
// has been answered with a Content-Length, until the read deadline already set on
|
||||
// the connection. It does this only for a body with a declared length from a
|
||||
// visitor that sent Connection: close, because Go's server reads none of that
|
||||
// rest. Without the read, the visitor, still sending, gets a reset and may never
|
||||
// read the answer. Go's server ends an answer without a Content-Length only once
|
||||
// the handler returns, so reading first would hold back the end of that answer.
|
||||
// Past 256 KiB, the MaxBytesReader made with Go's own ResponseWriter has Go's
|
||||
// server tell the visitor it sends no more, then wait 500 ms before it closes
|
||||
// the connection.
|
||||
func (rq *request) readRestOfBody() {
|
||||
if !rq.in.Close || rq.in.ContentLength <= 0 || rq.body.received.Load() ||
|
||||
rq.out.Header().Get("Content-Length") == "" ||
|
||||
rq.out.status == http.StatusSwitchingProtocols {
|
||||
return
|
||||
}
|
||||
|
||||
rest := http.MaxBytesReader(
|
||||
rq.out.ResponseWriter,
|
||||
rq.in.Body,
|
||||
maxBodyReadAfterAnswer,
|
||||
)
|
||||
_, _ = io.Copy(io.Discard, rest)
|
||||
}
|
||||
|
||||
// stopPassingOnBody ends the transport's reading of a visitor's body that has
|
||||
// not arrived whole, and lets Go's server read the rest until deadline. Go's
|
||||
// server reads it once the handler returns, but with no deadline at all if
|
||||
// another read of it is still waiting.
|
||||
// not arrived whole, and lets Go's server, or readRestOfBody, read the rest until
|
||||
// deadline. Go's server reads it once the handler returns, but with no deadline
|
||||
// at all if another read of it is still waiting.
|
||||
func (rq *request) stopPassingOnBody(deadline time.Time) {
|
||||
if rq.body == nil || rq.body.received.Load() {
|
||||
return
|
||||
|
||||
Reference in new issue
Block a user