Commit a3ffd836 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

net/http: pause briefly after closing Server connection when body remains

From https://github.com/golang/go/issues/11745#issuecomment-123555313
this implements option (b), having the server pause slightly after
sending the final response on a TCP connection when we're about to close
it when we know there's a request body outstanding. This biases the
client (which might not be Go) to prefer our response header over the
request body write error.

Updates #11745

Change-Id: I07cb0b74519d266c8049d9e0eb23a61304eedbf8
Reviewed-on: https://go-review.googlesource.com/12658Reviewed-by: default avatarRuss Cox <rsc@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
parent 249894ab
...@@ -3034,6 +3034,30 @@ Host: foo ...@@ -3034,6 +3034,30 @@ Host: foo
} }
} }
// If a Handler finishes and there's an unread request body,
// verify the server try to do implicit read on it before replying.
func TestHandlerFinishSkipBigContentLengthRead(t *testing.T) {
conn := &testConn{closec: make(chan bool)}
conn.readBuf.Write([]byte(fmt.Sprintf(
"POST / HTTP/1.1\r\n" +
"Host: test\r\n" +
"Content-Length: 9999999999\r\n" +
"\r\n" + strings.Repeat("a", 1<<20))))
ls := &oneConnListener{conn}
var inHandlerLen int
go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) {
inHandlerLen = conn.readBuf.Len()
rw.WriteHeader(404)
}))
<-conn.closec
afterHandlerLen := conn.readBuf.Len()
if afterHandlerLen != inHandlerLen {
t.Errorf("unexpected implicit read. Read buffer went from %d -> %d", inHandlerLen, afterHandlerLen)
}
}
func BenchmarkClientServer(b *testing.B) { func BenchmarkClientServer(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
b.StopTimer() b.StopTimer()
......
...@@ -874,8 +874,14 @@ func (cw *chunkWriter) writeHeader(p []byte) { ...@@ -874,8 +874,14 @@ func (cw *chunkWriter) writeHeader(p []byte) {
if w.req.ContentLength != 0 && !w.closeAfterReply { if w.req.ContentLength != 0 && !w.closeAfterReply {
ecr, isExpecter := w.req.Body.(*expectContinueReader) ecr, isExpecter := w.req.Body.(*expectContinueReader)
if !isExpecter || ecr.resp.wroteContinue { if !isExpecter || ecr.resp.wroteContinue {
var tooBig bool
if reqBody, ok := w.req.Body.(*body); ok && reqBody.unreadDataSize() >= maxPostHandlerReadBytes {
tooBig = true
} else {
n, _ := io.CopyN(ioutil.Discard, w.req.Body, maxPostHandlerReadBytes+1) n, _ := io.CopyN(ioutil.Discard, w.req.Body, maxPostHandlerReadBytes+1)
if n >= maxPostHandlerReadBytes { tooBig = n >= maxPostHandlerReadBytes
}
if tooBig {
w.requestTooLarge() w.requestTooLarge()
delHeader("Connection") delHeader("Connection")
setHeader.connection = "close" setHeader.connection = "close"
...@@ -1144,13 +1150,18 @@ func (w *response) shouldReuseConnection() bool { ...@@ -1144,13 +1150,18 @@ func (w *response) shouldReuseConnection() bool {
return false return false
} }
if body, ok := w.req.Body.(*body); ok && body.didEarlyClose() { if w.closedRequestBodyEarly() {
return false return false
} }
return true return true
} }
func (w *response) closedRequestBodyEarly() bool {
body, ok := w.req.Body.(*body)
return ok && body.didEarlyClose()
}
func (w *response) Flush() { func (w *response) Flush() {
if !w.wroteHeader { if !w.wroteHeader {
w.WriteHeader(StatusOK) w.WriteHeader(StatusOK)
...@@ -1318,7 +1329,7 @@ func (c *conn) serve() { ...@@ -1318,7 +1329,7 @@ func (c *conn) serve() {
} }
w.finishRequest() w.finishRequest()
if !w.shouldReuseConnection() { if !w.shouldReuseConnection() {
if w.requestBodyLimitHit { if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
c.closeWriteAndWait() c.closeWriteAndWait()
} }
break break
......
...@@ -737,6 +737,17 @@ func mergeSetHeader(dst *Header, src Header) { ...@@ -737,6 +737,17 @@ func mergeSetHeader(dst *Header, src Header) {
} }
} }
// unreadDataSize returns the number of bytes of unread input.
// It returns -1 if unknown.
func (b *body) unreadDataSize() int64 {
b.mu.Lock()
defer b.mu.Unlock()
if lr, ok := b.src.(*io.LimitedReader); ok {
return lr.N
}
return -1
}
func (b *body) Close() error { func (b *body) Close() error {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
......
...@@ -1164,7 +1164,7 @@ WaitResponse: ...@@ -1164,7 +1164,7 @@ WaitResponse:
for { for {
select { select {
case err := <-writeErrCh: case err := <-writeErrCh:
if isSyscallWriteError(err) { if isNetWriteError(err) {
// Issue 11745. If we failed to write the request // Issue 11745. If we failed to write the request
// body, it's possible the server just heard enough // body, it's possible the server just heard enough
// and already wrote to us. Prioritize the server's // and already wrote to us. Prioritize the server's
...@@ -1383,14 +1383,12 @@ type fakeLocker struct{} ...@@ -1383,14 +1383,12 @@ type fakeLocker struct{}
func (fakeLocker) Lock() {} func (fakeLocker) Lock() {}
func (fakeLocker) Unlock() {} func (fakeLocker) Unlock() {}
func isSyscallWriteError(err error) bool { func isNetWriteError(err error) bool {
switch e := err.(type) { switch e := err.(type) {
case *url.Error: case *url.Error:
return isSyscallWriteError(e.Err) return isNetWriteError(e.Err)
case *net.OpError: case *net.OpError:
return e.Op == "write" && isSyscallWriteError(e.Err) return e.Op == "write"
case *os.SyscallError:
return e.Syscall == "write"
default: default:
return false return false
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment