Commit b992c391 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

net/http: add NoBody, don't return nil from NewRequest on zero bodies

This is an alternate solution to https://golang.org/cl/31445

Instead of making NewRequest return a request with Request.Body == nil
to signal a zero byte body, add a well-known variable that means
explicitly zero.

Too many tests inside Google (and presumably the outside world)
broke.

Change-Id: I78f6ecca8e8aa1e12179c234ccfb6bcf0ee29ba8
Reviewed-on: https://go-review.googlesource.com/31726
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarJoe Tsai <thebrokentoaster@gmail.com>
parent 448e1db1
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package http package http
import ( import (
"io"
"strconv" "strconv"
"strings" "strings"
"time" "time"
...@@ -81,3 +82,21 @@ func hexEscapeNonASCII(s string) string { ...@@ -81,3 +82,21 @@ func hexEscapeNonASCII(s string) string {
} }
return string(b) return string(b)
} }
// NoBody is an io.ReadCloser with no bytes. Read always returns EOF
// and Close always returns nil. It can be used in an outgoing client
// request to explicitly signal that a request has zero bytes.
// An alternative, however, is to simply set Request.Body to nil.
var NoBody = noBody{}
type noBody struct{}
func (noBody) Read([]byte) (int, error) { return 0, io.EOF }
func (noBody) Close() error { return nil }
func (noBody) WriteTo(io.Writer) (int64, error) { return 0, nil }
var (
// verify that an io.Copy from NoBody won't require a buffer:
_ io.WriterTo = NoBody
_ io.ReadCloser = NoBody
)
...@@ -25,7 +25,7 @@ type reqTest struct { ...@@ -25,7 +25,7 @@ type reqTest struct {
} }
var noError = "" var noError = ""
var noBody = "" var noBodyStr = ""
var noTrailer Header = nil var noTrailer Header = nil
var reqTests = []reqTest{ var reqTests = []reqTest{
...@@ -95,7 +95,7 @@ var reqTests = []reqTest{ ...@@ -95,7 +95,7 @@ var reqTests = []reqTest{
RequestURI: "/", RequestURI: "/",
}, },
noBody, noBodyStr,
noTrailer, noTrailer,
noError, noError,
}, },
...@@ -121,7 +121,7 @@ var reqTests = []reqTest{ ...@@ -121,7 +121,7 @@ var reqTests = []reqTest{
RequestURI: "//user@host/is/actually/a/path/", RequestURI: "//user@host/is/actually/a/path/",
}, },
noBody, noBodyStr,
noTrailer, noTrailer,
noError, noError,
}, },
...@@ -131,7 +131,7 @@ var reqTests = []reqTest{ ...@@ -131,7 +131,7 @@ var reqTests = []reqTest{
"GET ../../../../etc/passwd HTTP/1.1\r\n" + "GET ../../../../etc/passwd HTTP/1.1\r\n" +
"Host: test\r\n\r\n", "Host: test\r\n\r\n",
nil, nil,
noBody, noBodyStr,
noTrailer, noTrailer,
"parse ../../../../etc/passwd: invalid URI for request", "parse ../../../../etc/passwd: invalid URI for request",
}, },
...@@ -141,7 +141,7 @@ var reqTests = []reqTest{ ...@@ -141,7 +141,7 @@ var reqTests = []reqTest{
"GET HTTP/1.1\r\n" + "GET HTTP/1.1\r\n" +
"Host: test\r\n\r\n", "Host: test\r\n\r\n",
nil, nil,
noBody, noBodyStr,
noTrailer, noTrailer,
"parse : empty url", "parse : empty url",
}, },
...@@ -227,7 +227,7 @@ var reqTests = []reqTest{ ...@@ -227,7 +227,7 @@ var reqTests = []reqTest{
RequestURI: "www.google.com:443", RequestURI: "www.google.com:443",
}, },
noBody, noBodyStr,
noTrailer, noTrailer,
noError, noError,
}, },
...@@ -251,7 +251,7 @@ var reqTests = []reqTest{ ...@@ -251,7 +251,7 @@ var reqTests = []reqTest{
RequestURI: "127.0.0.1:6060", RequestURI: "127.0.0.1:6060",
}, },
noBody, noBodyStr,
noTrailer, noTrailer,
noError, noError,
}, },
...@@ -275,7 +275,7 @@ var reqTests = []reqTest{ ...@@ -275,7 +275,7 @@ var reqTests = []reqTest{
RequestURI: "/_goRPC_", RequestURI: "/_goRPC_",
}, },
noBody, noBodyStr,
noTrailer, noTrailer,
noError, noError,
}, },
...@@ -299,7 +299,7 @@ var reqTests = []reqTest{ ...@@ -299,7 +299,7 @@ var reqTests = []reqTest{
RequestURI: "*", RequestURI: "*",
}, },
noBody, noBodyStr,
noTrailer, noTrailer,
noError, noError,
}, },
...@@ -323,7 +323,7 @@ var reqTests = []reqTest{ ...@@ -323,7 +323,7 @@ var reqTests = []reqTest{
RequestURI: "*", RequestURI: "*",
}, },
noBody, noBodyStr,
noTrailer, noTrailer,
noError, noError,
}, },
...@@ -350,7 +350,7 @@ var reqTests = []reqTest{ ...@@ -350,7 +350,7 @@ var reqTests = []reqTest{
RequestURI: "/", RequestURI: "/",
}, },
noBody, noBodyStr,
noTrailer, noTrailer,
noError, noError,
}, },
...@@ -376,7 +376,7 @@ var reqTests = []reqTest{ ...@@ -376,7 +376,7 @@ var reqTests = []reqTest{
RequestURI: "/", RequestURI: "/",
}, },
noBody, noBodyStr,
noTrailer, noTrailer,
noError, noError,
}, },
...@@ -397,7 +397,7 @@ var reqTests = []reqTest{ ...@@ -397,7 +397,7 @@ var reqTests = []reqTest{
ContentLength: -1, ContentLength: -1,
Close: true, Close: true,
}, },
noBody, noBodyStr,
noTrailer, noTrailer,
noError, noError,
}, },
......
...@@ -771,10 +771,14 @@ func NewRequest(method, urlStr string, body io.Reader) (*Request, error) { ...@@ -771,10 +771,14 @@ func NewRequest(method, urlStr string, body io.Reader) (*Request, error) {
// For client requests, Request.ContentLength of 0 // For client requests, Request.ContentLength of 0
// means either actually 0, or unknown. The only way // means either actually 0, or unknown. The only way
// to explicitly say that the ContentLength is zero is // to explicitly say that the ContentLength is zero is
// to set the Body to nil. // to set the Body to nil. But turns out too much code
// depends on NewRequest returning a non-nil Body,
// so we use a well-known ReadCloser variable instead
// and have the http package also treat that sentinel
// variable to mean explicitly zero.
if req.ContentLength == 0 { if req.ContentLength == 0 {
req.Body = nil req.Body = NoBody
req.GetBody = nil req.GetBody = func() (io.ReadCloser, error) { return NoBody, nil }
} }
} }
...@@ -1252,7 +1256,7 @@ func (r *Request) isReplayable() bool { ...@@ -1252,7 +1256,7 @@ func (r *Request) isReplayable() bool {
// outgoingLength reports the Content-Length of this outgoing (Client) request. // outgoingLength reports the Content-Length of this outgoing (Client) request.
// It maps 0 into -1 (unknown) when the Body is non-nil. // It maps 0 into -1 (unknown) when the Body is non-nil.
func (r *Request) outgoingLength() int64 { func (r *Request) outgoingLength() int64 {
if r.Body == nil { if r.Body == nil || r.Body == NoBody {
return 0 return 0
} }
if r.ContentLength != 0 { if r.ContentLength != 0 {
......
...@@ -511,7 +511,7 @@ func TestNewRequestContentLength(t *testing.T) { ...@@ -511,7 +511,7 @@ func TestNewRequestContentLength(t *testing.T) {
if req.ContentLength != tt.want { if req.ContentLength != tt.want {
t.Errorf("test[%d]: ContentLength(%T) = %d; want %d", i, tt.r, req.ContentLength, tt.want) t.Errorf("test[%d]: ContentLength(%T) = %d; want %d", i, tt.r, req.ContentLength, tt.want)
} }
if (req.ContentLength == 0) != (req.Body == nil) { if (req.ContentLength == 0) != (req.Body == NoBody) {
t.Errorf("test[%d]: ContentLength = %d but Body non-nil is %v", i, req.ContentLength, req.Body != nil) t.Errorf("test[%d]: ContentLength = %d but Body non-nil is %v", i, req.ContentLength, req.Body != nil)
} }
} }
......
...@@ -261,7 +261,7 @@ func (r *Response) Write(w io.Writer) error { ...@@ -261,7 +261,7 @@ func (r *Response) Write(w io.Writer) error {
if n == 0 { if n == 0 {
// Reset it to a known zero reader, in case underlying one // Reset it to a known zero reader, in case underlying one
// is unhappy being read repeatedly. // is unhappy being read repeatedly.
r1.Body = eofReader r1.Body = NoBody
} else { } else {
r1.ContentLength = -1 r1.ContentLength = -1
r1.Body = struct { r1.Body = struct {
......
...@@ -1776,7 +1776,7 @@ func registerOnHitEOF(rc io.ReadCloser, fn func()) { ...@@ -1776,7 +1776,7 @@ func registerOnHitEOF(rc io.ReadCloser, fn func()) {
// requestBodyRemains reports whether future calls to Read // requestBodyRemains reports whether future calls to Read
// on rc might yield more data. // on rc might yield more data.
func requestBodyRemains(rc io.ReadCloser) bool { func requestBodyRemains(rc io.ReadCloser) bool {
if rc == eofReader { if rc == NoBody {
return false return false
} }
switch v := rc.(type) { switch v := rc.(type) {
...@@ -2702,24 +2702,6 @@ func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) { ...@@ -2702,24 +2702,6 @@ func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) {
} }
} }
type eofReaderWithWriteTo struct{}
func (eofReaderWithWriteTo) WriteTo(io.Writer) (int64, error) { return 0, nil }
func (eofReaderWithWriteTo) Read([]byte) (int, error) { return 0, io.EOF }
// eofReader is a non-nil io.ReadCloser that always returns EOF.
// It has a WriteTo method so io.Copy won't need a buffer.
var eofReader = &struct {
eofReaderWithWriteTo
io.Closer
}{
eofReaderWithWriteTo{},
ioutil.NopCloser(nil),
}
// Verify that an io.Copy from an eofReader won't require a buffer.
var _ io.WriterTo = eofReader
// initNPNRequest is an HTTP handler that initializes certain // initNPNRequest is an HTTP handler that initializes certain
// uninitialized fields in its *Request. Such partially-initialized // uninitialized fields in its *Request. Such partially-initialized
// Requests come from NPN protocol handlers. // Requests come from NPN protocol handlers.
...@@ -2734,7 +2716,7 @@ func (h initNPNRequest) ServeHTTP(rw ResponseWriter, req *Request) { ...@@ -2734,7 +2716,7 @@ func (h initNPNRequest) ServeHTTP(rw ResponseWriter, req *Request) {
*req.TLS = h.c.ConnectionState() *req.TLS = h.c.ConnectionState()
} }
if req.Body == nil { if req.Body == nil {
req.Body = eofReader req.Body = NoBody
} }
if req.RemoteAddr == "" { if req.RemoteAddr == "" {
req.RemoteAddr = h.c.RemoteAddr().String() req.RemoteAddr = h.c.RemoteAddr().String()
......
...@@ -367,12 +367,12 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) { ...@@ -367,12 +367,12 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) {
switch { switch {
case chunked(t.TransferEncoding): case chunked(t.TransferEncoding):
if noBodyExpected(t.RequestMethod) { if noBodyExpected(t.RequestMethod) {
t.Body = eofReader t.Body = NoBody
} else { } else {
t.Body = &body{src: internal.NewChunkedReader(r), hdr: msg, r: r, closing: t.Close} t.Body = &body{src: internal.NewChunkedReader(r), hdr: msg, r: r, closing: t.Close}
} }
case realLength == 0: case realLength == 0:
t.Body = eofReader t.Body = NoBody
case realLength > 0: case realLength > 0:
t.Body = &body{src: io.LimitReader(r, realLength), closing: t.Close} t.Body = &body{src: io.LimitReader(r, realLength), closing: t.Close}
default: default:
...@@ -382,7 +382,7 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) { ...@@ -382,7 +382,7 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) {
t.Body = &body{src: r, closing: t.Close} t.Body = &body{src: r, closing: t.Close}
} else { } else {
// Persistent connection (i.e. HTTP/1.1) // Persistent connection (i.e. HTTP/1.1)
t.Body = eofReader t.Body = NoBody
} }
} }
......
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