Leo Alekseyev
Leo Alekseyev

Reputation: 13483

HTTP PUT request body from a file in golang: upload fails without properly setting ContentLength

I am trying to issue a simple PUT request to upload a file. http.NewRequest accepts body (as an io.Reader). But passing os.File as body is NOT working, whereas reading it into a buffer first does the trick:

file, _ := os.Open(filePath)

// request, _ := http.NewRequest("PUT", myURL, file)
// ^^^ why does this not work???

var buf bytes.Buffer
tee := io.TeeReader(file, &buf)
ioutil.ReadAll(tee)                                 
request, _ := http.NewRequest("PUT", myURL, &buf)   // this works fine

request.Header.Set("Content-Type", "application/octet-stream")
http.DefaultClient.Do(request)

EDIT: the issue was not setting the ContentLength header (i.e. it was set to the default of 0); this resulted in server not processing the upload. When using a buffer, golang does set the header to buffer length, resulting in different behavior.

Is the ContentLength header semantics server dependent?? Browsing SO I got the impression that the header is optional, which is clearly not the case here.

Upvotes: 1

Views: 1858

Answers (1)

p1gd0g
p1gd0g

Reputation: 711

This source code may help.

    // /usr/lib/go/src/net/http/request.go:872
    if body != nil {
        switch v := body.(type) {
        case *bytes.Buffer:
            req.ContentLength = int64(v.Len())
            buf := v.Bytes()
            req.GetBody = func() (io.ReadCloser, error) {
                r := bytes.NewReader(buf)
                return ioutil.NopCloser(r), nil
            }
        case *bytes.Reader:
            req.ContentLength = int64(v.Len())
            snapshot := *v
            req.GetBody = func() (io.ReadCloser, error) {
                r := snapshot
                return ioutil.NopCloser(&r), nil
            }
        case *strings.Reader:
            req.ContentLength = int64(v.Len())
            snapshot := *v
            req.GetBody = func() (io.ReadCloser, error) {
                r := snapshot
                return ioutil.NopCloser(&r), nil
            }
        default:
            // This is where we'd set it to -1 (at least
            // if body != NoBody) to mean unknown, but
            // that broke people during the Go 1.8 testing
            // period. People depend on it being 0 I
            // guess. Maybe retry later. See Issue 18117.
        }
        // For client requests, Request.ContentLength of 0
        // means either actually 0, or unknown. The only way
        // to explicitly say that the ContentLength is zero is
        // 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.GetBody != nil && req.ContentLength == 0 {
            req.Body = NoBody
            req.GetBody = func() (io.ReadCloser, error) { return NoBody, nil }
        }
    }

Upvotes: 1

Related Questions