return nil, didTimeout, fmt.Errorf("http: RoundTripper implementation (%T) returned a nil *Response with a nil error", rt)
}
if resp.Body == nil {
- return nil, didTimeout, fmt.Errorf("http: RoundTripper implementation (%T) returned a *Response with a nil Body", rt)
+ // The documentation on the Body field says “The http Client and Transport
+ // guarantee that Body is always non-nil, even on responses without a body
+ // or responses with a zero-length body.” Unfortunately, we didn't document
+ // that same constraint for arbitrary RoundTripper implementations, and
+ // RoundTripper implementations in the wild (mostly in tests) assume that
+ // they can use a nil Body to mean an empty one (similar to Request.Body).
+ // (See https://golang.org/issue/38095.)
+ //
+ // If the ContentLength allows the Body to be empty, fill in an empty one
+ // here to ensure that it is non-nil.
+ if resp.ContentLength > 0 && req.Method != "HEAD" {
+ return nil, didTimeout, fmt.Errorf("http: RoundTripper implementation (%T) returned a *Response with content length %d but a nil Body", rt, resp.ContentLength)
+ }
+ resp.Body = ioutil.NopCloser(strings.NewReader(""))
}
if !deadline.IsZero() {
resp.Body = &cancelTimerBody{
})
}
}
+
+type nilBodyRoundTripper struct{}
+
+func (nilBodyRoundTripper) RoundTrip(req *Request) (*Response, error) {
+ return &Response{
+ StatusCode: StatusOK,
+ Status: StatusText(StatusOK),
+ Body: nil,
+ Request: req,
+ }, nil
+}
+
+func TestClientPopulatesNilResponseBody(t *testing.T) {
+ c := &Client{Transport: nilBodyRoundTripper{}}
+
+ resp, err := c.Get("http://localhost/anything")
+ if err != nil {
+ t.Fatalf("Client.Get rejected Response with nil Body: %v", err)
+ }
+
+ if resp.Body == nil {
+ t.Fatalf("Client failed to provide a non-nil Body as documented")
+ }
+ defer func() {
+ if err := resp.Body.Close(); err != nil {
+ t.Fatalf("error from Close on substitute Response.Body: %v", err)
+ }
+ }()
+
+ if b, err := ioutil.ReadAll(resp.Body); err != nil {
+ t.Errorf("read error from substitute Response.Body: %v", err)
+ } else if len(b) != 0 {
+ t.Errorf("substitute Response.Body was unexpectedly non-empty: %q", b)
+ }
+}