net/http: refactor testing of Request.Body on 0 ContentLength

Code movement only, to look more like the equivalent http2 code, and
to make an upcoming fix look more obvious.

Updates #16002 (to be fixed once this code is in)

Change-Id: Iaa4f965be14e98f9996e7c4624afe6e19bed1a80
Reviewed-on: https://go-review.googlesource.com/30087
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
This commit is contained in:
Brad Fitzpatrick 2016-09-30 10:40:58 -07:00
parent ab6ba99484
commit 9491e7d65e
2 changed files with 46 additions and 27 deletions

View file

@ -1215,3 +1215,42 @@ func (r *Request) isReplayable() bool {
}
return false
}
// bodyAndLength reports the request's body and content length, with
// the difference from r.ContentLength being that 0 means actually
// zero, and -1 means unknown.
func (r *Request) bodyAndLength() (body io.Reader, contentLen int64) {
body = r.Body
if body == nil {
return nil, 0
}
if r.ContentLength != 0 {
return body, r.ContentLength
}
// Don't try to sniff the bytes if they're using a custom
// transfer encoding (or specified chunked themselves), and
// don't sniff if they're not using HTTP/1.1 and can't chunk
// anyway.
if len(r.TransferEncoding) != 0 || !r.ProtoAtLeast(1, 1) {
return body, -1
}
// Test to see if it's actually zero or just unset.
var buf [1]byte
n, err := io.ReadFull(body, buf[:])
if err != nil && err != io.EOF {
return errorReader{err}, -1
}
if n == 1 {
// Oh, guess there is data in this Body Reader after all.
// The ContentLength field just wasn't set.
// Stich the Body back together again, re-attaching our
// consumed byte.
// TODO(bradfitz): switch to stitchByteAndReader
return io.MultiReader(bytes.NewReader(buf[:]), body), -1
}
// Body is actually empty.
return nil, 0
}

View file

@ -59,37 +59,17 @@ func newTransferWriter(r interface{}) (t *transferWriter, err error) {
return nil, fmt.Errorf("http: Request.ContentLength=%d with nil Body", rr.ContentLength)
}
t.Method = valueOrDefault(rr.Method, "GET")
t.Body = rr.Body
t.BodyCloser = rr.Body
t.ContentLength = rr.ContentLength
t.Close = rr.Close
t.TransferEncoding = rr.TransferEncoding
t.Trailer = rr.Trailer
atLeastHTTP11 = rr.ProtoAtLeast(1, 1)
if t.Body != nil && len(t.TransferEncoding) == 0 && atLeastHTTP11 {
if t.ContentLength == 0 {
// Test to see if it's actually zero or just unset.
var buf [1]byte
n, rerr := io.ReadFull(t.Body, buf[:])
if rerr != nil && rerr != io.EOF {
t.ContentLength = -1
t.Body = errorReader{rerr}
} else if n == 1 {
// Oh, guess there is data in this Body Reader after all.
// The ContentLength field just wasn't set.
// Stich the Body back together again, re-attaching our
// consumed byte.
t.ContentLength = -1
t.Body = io.MultiReader(bytes.NewReader(buf[:]), t.Body)
} else {
// Body is actually empty.
t.Body = nil
t.BodyCloser = nil
}
}
if t.ContentLength < 0 {
t.TransferEncoding = []string{"chunked"}
}
t.Body, t.ContentLength = rr.bodyAndLength()
if t.Body != nil {
t.BodyCloser = rr.Body
}
if t.ContentLength < 0 && len(t.TransferEncoding) == 0 && atLeastHTTP11 {
t.TransferEncoding = []string{"chunked"}
}
case *Response:
t.IsResponse = true