Skip to content

Commit d6bdda2

Browse files
committed
net/http: pause briefly after closing Server connection when body remains
From golang/go#11745 (comment) 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/12658 Reviewed-by: Russ Cox <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]>
1 parent c4d5da3 commit d6bdda2

File tree

4 files changed

+54
-10
lines changed

4 files changed

+54
-10
lines changed

serve_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3034,6 +3034,30 @@ Host: foo
30343034
}
30353035
}
30363036

3037+
// If a Handler finishes and there's an unread request body,
3038+
// verify the server try to do implicit read on it before replying.
3039+
func TestHandlerFinishSkipBigContentLengthRead(t *testing.T) {
3040+
conn := &testConn{closec: make(chan bool)}
3041+
conn.readBuf.Write([]byte(fmt.Sprintf(
3042+
"POST / HTTP/1.1\r\n" +
3043+
"Host: test\r\n" +
3044+
"Content-Length: 9999999999\r\n" +
3045+
"\r\n" + strings.Repeat("a", 1<<20))))
3046+
3047+
ls := &oneConnListener{conn}
3048+
var inHandlerLen int
3049+
go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) {
3050+
inHandlerLen = conn.readBuf.Len()
3051+
rw.WriteHeader(404)
3052+
}))
3053+
<-conn.closec
3054+
afterHandlerLen := conn.readBuf.Len()
3055+
3056+
if afterHandlerLen != inHandlerLen {
3057+
t.Errorf("unexpected implicit read. Read buffer went from %d -> %d", inHandlerLen, afterHandlerLen)
3058+
}
3059+
}
3060+
30373061
func BenchmarkClientServer(b *testing.B) {
30383062
b.ReportAllocs()
30393063
b.StopTimer()

server.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -874,8 +874,14 @@ func (cw *chunkWriter) writeHeader(p []byte) {
874874
if w.req.ContentLength != 0 && !w.closeAfterReply {
875875
ecr, isExpecter := w.req.Body.(*expectContinueReader)
876876
if !isExpecter || ecr.resp.wroteContinue {
877-
n, _ := io.CopyN(ioutil.Discard, w.req.Body, maxPostHandlerReadBytes+1)
878-
if n >= maxPostHandlerReadBytes {
877+
var tooBig bool
878+
if reqBody, ok := w.req.Body.(*body); ok && reqBody.unreadDataSize() >= maxPostHandlerReadBytes {
879+
tooBig = true
880+
} else {
881+
n, _ := io.CopyN(ioutil.Discard, w.req.Body, maxPostHandlerReadBytes+1)
882+
tooBig = n >= maxPostHandlerReadBytes
883+
}
884+
if tooBig {
879885
w.requestTooLarge()
880886
delHeader("Connection")
881887
setHeader.connection = "close"
@@ -1144,13 +1150,18 @@ func (w *response) shouldReuseConnection() bool {
11441150
return false
11451151
}
11461152

1147-
if body, ok := w.req.Body.(*body); ok && body.didEarlyClose() {
1153+
if w.closedRequestBodyEarly() {
11481154
return false
11491155
}
11501156

11511157
return true
11521158
}
11531159

1160+
func (w *response) closedRequestBodyEarly() bool {
1161+
body, ok := w.req.Body.(*body)
1162+
return ok && body.didEarlyClose()
1163+
}
1164+
11541165
func (w *response) Flush() {
11551166
if !w.wroteHeader {
11561167
w.WriteHeader(StatusOK)
@@ -1318,7 +1329,7 @@ func (c *conn) serve() {
13181329
}
13191330
w.finishRequest()
13201331
if !w.shouldReuseConnection() {
1321-
if w.requestBodyLimitHit {
1332+
if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
13221333
c.closeWriteAndWait()
13231334
}
13241335
break

transfer.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,17 @@ func mergeSetHeader(dst *Header, src Header) {
737737
}
738738
}
739739

740+
// unreadDataSize returns the number of bytes of unread input.
741+
// It returns -1 if unknown.
742+
func (b *body) unreadDataSize() int64 {
743+
b.mu.Lock()
744+
defer b.mu.Unlock()
745+
if lr, ok := b.src.(*io.LimitedReader); ok {
746+
return lr.N
747+
}
748+
return -1
749+
}
750+
740751
func (b *body) Close() error {
741752
b.mu.Lock()
742753
defer b.mu.Unlock()

transport.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,7 +1164,7 @@ WaitResponse:
11641164
for {
11651165
select {
11661166
case err := <-writeErrCh:
1167-
if isSyscallWriteError(err) {
1167+
if isNetWriteError(err) {
11681168
// Issue 11745. If we failed to write the request
11691169
// body, it's possible the server just heard enough
11701170
// and already wrote to us. Prioritize the server's
@@ -1383,14 +1383,12 @@ type fakeLocker struct{}
13831383
func (fakeLocker) Lock() {}
13841384
func (fakeLocker) Unlock() {}
13851385

1386-
func isSyscallWriteError(err error) bool {
1386+
func isNetWriteError(err error) bool {
13871387
switch e := err.(type) {
13881388
case *url.Error:
1389-
return isSyscallWriteError(e.Err)
1389+
return isNetWriteError(e.Err)
13901390
case *net.OpError:
1391-
return e.Op == "write" && isSyscallWriteError(e.Err)
1392-
case *os.SyscallError:
1393-
return e.Syscall == "write"
1391+
return e.Op == "write"
13941392
default:
13951393
return false
13961394
}

0 commit comments

Comments
 (0)