Skip to content

net/http: Transport should close body on all errors and match Client.Do docs #35015

@odeke-em

Description

@odeke-em

I just saw CL https://go-review.googlesource.com/c/go/+/202237 by @bored-engineer (thank you Luke!) and was going to review and recommend/create a test for the CL creator.

The docs for http.Client.Do https://golang.org/pkg/net/http/#Client.Do promise that we'll always close the body even on errors and so far we missed a few spots on the trivial cases with minor oversight

if isHTTP {
for k, vv := range req.Header {
if !httpguts.ValidHeaderFieldName(k) {
return nil, fmt.Errorf("net/http: invalid header field name %q", k)
}
for _, v := range vv {
if !httpguts.ValidHeaderFieldValue(v) {
return nil, fmt.Errorf("net/http: invalid header field value %q for key %v", v, k)
}
}
}
}
if t.useRegisteredProtocol(req) {
altProto, _ := t.altProto.Load().(map[string]RoundTripper)
if altRT := altProto[scheme]; altRT != nil {
if resp, err := altRT.RoundTrip(req); err != ErrSkipAltProtocol {
return resp, err
}
}
}
if !isHTTP {
req.closeBody()
return nil, &badStringError{"unsupported protocol scheme", scheme}
}
if req.Method != "" && !validMethod(req.Method) {
return nil, fmt.Errorf("net/http: invalid method %q", req.Method)

that is:
a) if isHTTP {
b) if t.useRegisteredProtocol(req)
c) if req.Method != "" && !validMethod(req.Method) {

and I realize this is because that code has been modified piecemeal over the past decade.

This test is what we can use to bootstrap checks

package main

import (
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
)

type bodyCloser bool

func (bc *bodyCloser) Close() error {
	*bc = true
        return nil
}

func (bc *bodyCloser) Read(b []byte) (n int, err error) {
	return 0, io.EOF
}

func TestInvalidMethodClosesBody(t *testing.T) {
	cst := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
	defer cst.Close()
	var bc bodyCloser

	u, _ := url.Parse(cst.URL)
	req := &http.Request{
		Method: " ",
		URL:    u,
		Body:   &bc,
	}

	_, err := http.DefaultClient.Do(req)
	if err == nil {
		t.Fatal("Expected an error")
	}
	if !bc {
		t.Fatal("Expected body to have been closed")
	}
}

which currently gives

$ go test
--- FAIL: TestInvalidMethodClosesBody (0.00s)
    it_test.go:39: Expected body to have been closed

This issue will be to send CLs to enforce those closes as well as tests so that we don't regress.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.help wanted

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions