Search code examples
gotwittermultipartform-datamultipartform-fields

Multipart file field is unreadable


I am trying to upload photos to Twitter. I created a multipart writer and creating a file field using that named media but when I send my request to Twitter it keeps responding missing media field. Am I missing something? Here is my code

    f, err := os.Open("/Users/nikos/Desktop/test.png")
errored:
    if nil != err {
        fmt.Println(err)
        return
    }
    var img = new(bytes.Buffer)
    enc := base64.NewEncoder(base64.StdEncoding, img)
    _, err = io.Copy(enc, f)
    if nil != err {
        goto errored
    }
    body := new(bytes.Buffer)//Multipart body
    writer := multipart.NewWriter(body)
    cl, err := twitter.OauthClient.MakeHttpClient(&oauth.AccessToken{
        Token:  "xxx",
        Secret: "yyy",
    })
    err = writer.WriteField("media_data", img.String())//base64 version of the image (i tried both binary and base64 versions neither will work)
    if nil != err {
        goto errored
    }
    part, err := writer.CreateFormFile("media", "test.png")//actual binary file multiparted and it is named media.
    if nil != err {
        goto errored
    }
    _, err = io.Copy(part, f)
    if nil != err {
        goto errored
    }
    req, err := http.NewRequest("POST",
        "https://upload.twitter.com/1.1/media/upload.json",
        body)
    if nil != err {
        goto errored
    }
    res, err := cl.Do(req)
    if nil != err {
        goto errored
    }
//and twitter responds that there is no field attached named media
    _, err = io.Copy(os.Stdout, res.Body)
    fmt.Println(res)
    if nil != err {
        goto errored
    } 

Solution

  • Updates: Just referred Twitter API Upload parameter. As per your code snippet you're using both fields media and media_data. You have to use only one -

    • Upload using base64 -> field name is media_data
    • Upload using raw -> field name is media

    And, you have to add Content-Type header.

    req, err := http.NewRequest("POST",
        "https://upload.twitter.com/1.1/media/upload.json",
        body)
    req.Header.Set("Content-Type", writer.FormDataContentType())
    if err := writer.Close(); err != nil {
       log.Println(err)
    }
    // Now fire the http request
    

    PS: While composing an answer, in 30 secs gap, @cerise-limón added comment, also close the multipart writer as mentioned by @cerise-limón.


    Asked in the comment:

    Twitter accepts application/octet-stream, you may not need below approach.

    Adding multi-part with user supplied Content-Type instead of application/octet-stream. Basically you have to do same implementation as convenience wrapper with your content-type.

    writer := multipart.NewWriter(body)
    h := make(textproto.MIMEHeader)
    h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
            escapeQuotes(fieldname), escapeQuotes(filename)))
    h.Set("Content-Type", "image/png")
    part, err := writer.CreatePart(h)
    // use part same as before
    

    Definition of escapeQuotes from multiple-part package.

    var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
    func escapeQuotes(s string) string {
        return quoteEscaper.Replace(s)
    }