Golang * bytes.Buffer nil会导致致命错误[关闭]

I had the same issue as https://github.com/golang/go/issues/26666 because I have a wrap function for my http requests.

Sometimes I need to request:

body := new(bytes.Buffer)
json.NewEncoder(body).Encode(h)
req("POST", "http://example.com", body)

And sometimes it's simply:

req("GET", "http://example.com", nil)
runtime error: invalid memory address or nil pointer dereference

I ended up with:

req("GET", "http://example.com", new(bytes.Buffer))

But I'm not sure if it's the right thing to do.

The function:

func req(method string, url string, body *bytes.Buffer) int {
req, err := http.NewRequest(method, url, body)
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth(user, psw)
resp, err := client.Do(req)
checkErr(err)
if resp.StatusCode > 500 {
    time.Sleep(30 * time.Second)
    resp, err = client.Do(req)
    checkErr(err)
}
defer resp.Body.Close()
return resp.StatusCode
}

Updated function:

func req(method string, url string, body io.Reader) int {
    req, err := http.NewRequest(method, url, body)
    req.Header.Set("Content-Type", "application/json")
    req.SetBasicAuth(user, psw)
    resp, err := client.Do(req)
    checkErr(err)
    defer resp.Body.Close()
    if resp.StatusCode >= 500 {
        time.Sleep(30 * time.Second)
        req, err := http.NewRequest(method, url, body)
        req.Header.Set("Content-Type", "application/json")
        req.SetBasicAuth(user, psw)
        resp, err := client.Do(req)
        checkErr(err)
        defer resp.Body.Close()
    }
    return resp.StatusCode
}

func checkErr(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

The body in http.NewRequest() is optional, so passing nil is acceptable when you're doing GET requests.

The problem is that the body parameter of http.NewRequest is an interface type: io.Reader, and you're attempting to pass a value of a concrete type *bytes.Buffer. What happens is that this nil pointer will be wrapped in a non-nil interface value, and that will be passed to http.NewRequest as the body.

If you don't have a body, pass nil explicitly, like this:

func req(method string, url string, body *bytes.Buffer) int {
    var bodyToPass io.Reader
    if body != nil {
        bodyToPass = body
    }
    req, err := http.NewRequest(method, url, bodyToPass)

    // ....
}

And then you can call it like:

req("GET", "http://example.com", nil)

Although best would be if your req() function would take io.Reader in the first place, so you don't have to check its value explicitly:

func req(method string, url string, body io.Reader) int {
    req, err := http.NewRequest(method, url, body) // You may pass it as-is

    // ....
}

And you can call it with nil or with a non-nil *bytes.Buffer too:

req("GET", "http://example.com", nil) // OK

req("POST", "http://example.com", bytes.NewBufferString("data")) // Also OK

For more details, see Hiding nil values, understanding why golang fails here