'Golang: io.Reader w/io.NopCloser

I cannot for the life of me figure out why this isn't working... it returns a 404... however it works when I remove io.NopCloser

200: req, err := http.NewRequest("POST", uri, body)

404: req, err := http.NewRequest("POST", uri, io.NopCloser(body))

Why does this matter? Because while the signature of NewRequest accepts io.Reader inside the method its converted to a io.ReadCloser

https://github.com/golang/go/blob/e94f7df957b6cfbdfbed7092fd05628452c5e018/src/net/http/request.go#L873

    rc, ok := body.(io.ReadCloser)
    if !ok && body != nil {
        rc = io.NopCloser(body)
    }

Additionally the property Request.Body is of type io.ReadCloser (not io.Reader)

https://github.com/golang/go/blob/e94f7df957b6cfbdfbed7092fd05628452c5e018/src/net/http/request.go#L182

Ultimately my goal is to acheive this... hence why I do not want to set body when calling http.NewRequest

req, err := http.NewRequest("POST", uri, nil)

if someCondition {
    req.Body = getBody()//some method that returns a io.ReaderCloser
}

//the real getBody method is more complex than this...
func getBody() io.ReaderCloser {
    data := map[string][]string{
        "scope":         {"https://graph.microsoft.com/.default"},
        "client_id":     {"0000-0000-0000-0000"},
        "client_secret": {"REDACTED_CLIENT_SECRET"},
        "grant_type":    {"client_credentials"},
    }

    return io.NopCloser(strings.NewReader(url.Values(data).Encode()))
}

Complete code that returns a 404 below...

    uri := "https://login.microsoftonline.com/REDACTED.onmicrosoft.com/oauth2/v2.0/token"

    data := map[string][]string{
        "scope":         {"https://graph.microsoft.com/.default"},
        "client_id":     {"0000-0000-0000-0000"},
        "client_secret": {"REDACTED_CLIENT_SECRET"},
        "grant_type":    {"client_credentials"},
    }

    body := strings.NewReader(url.Values(data).Encode())

    req, err := http.NewRequest("POST", uri, io.NopCloser(body))
    if err != nil {
        panic(err)
    }

    client := &http.Client{Timeout: 10 * time.Second}
    res, err := client.Do(req)
    if err != nil {
        panic(err)
    }

    b, err := io.ReadAll(res.Body)
    if err != nil {
        panic(err)
    }

    fmt.Println(res.StatusCode)
    fmt.Println(string(b))
go


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source