Composing multipart/form-data with a different Content-Type on each parts with Javascript (or Angular)

86,576

Solution 1

According to the documentation of FormData, you can append a field with a specific content type by using the Blob constructor:

var formData = new FormData();

formData.append('items', new Blob([JSON.stringify({
    name: "Book",
    quantity: "12"
})], {
    type: "application/json"
}));

After careful observation, it turns out that it will send the part as follows:

Content-Disposition: form-data; name="items"; filename="blob"
Content-Type: text/json

The only alternative, safe from building the whole request yourself is to pass a string value:

formData.append('items', '{"name": "Book", "quantity": "12"}');

This, unfortunately, doesn't set the Content-Type header.

Solution 2

Mistake #1: I mistakenly assume that the items has to be a json, so that we can call its attribute.

Solution: To submit a multipart request that contain a file and an object like format is very simple.

form = new FormData();
form.append('items[name]', 'Book');
form.append('items[quantity]', 12);
form.append('image', imageFile);
form.append('owner', 'John Doe');

So thus the request header and body will looks something like this

POST /api/v1/inventory
Host: localhost:8000
Origin: http://localhost:9000
Content-Type: multipart/form-data; boundary=------border

------border
Content-Disposition: form-data; name="owner"

john doe
------border
Content-Disposition: form-data; name="image"; filename="mybook.png"
Content-Type: image/png


------border
Content-Disposition: form-data; name="items[name]"

Book
------border
Content-Disposition: form-data; name="items[quantity]"

12
------border--

Solution 3

Nothing would get this to work, until I set the Content-Type header to undefined. In my case I am posting a file and some json.

public uploadFile(code: string, file):angular.IHttpPromise<any>{
    var data = `{"query":"mutation FIRMSCORECARD_CALCULATE($code:String!){ FirmScorecardMutation{ BatchCalculate(Code:$code) }}","variables":{"code":"${code}"},"operationName":"FIRMSCORECARD_CALCULATE"}`;
    var formData = new FormData();
    formData.append('operations', data);
    formData.append('file', file, file.name);

    let config = {
        headers: {
            'Accept': 'application/json',
            'Content-Type': undefined
        }
    };
    let response = this.$http.post(this.graphqlUrl, formData, config);
    return response;
}
Share:
86,576
Yeo
Author by

Yeo

Abstraction Free is better then Open. Open is better than Close. Simple is better than Complex. Complex is better than Complicated Do one things and do it well, or don't do it at all. Always be friendly in communities so that you can help better in solving their problem. About Me I am Eugene. Worked in Autodesk. I am well rounded programmer, specialize in Python &amp; JavaScript.

Updated on July 10, 2022

Comments

  • Yeo
    Yeo almost 2 years

    Wrong question asked, see my update below

    I need to integrate my AngularJS Project with an existing RESTful API. These API consume POST request which upload a file, and also submit the form data in a request. Unfortunately, one of the form input requires to be in Content-Type: Application/json.

    After search on the web, I could only POST with Content-Type: multipart/form-data in which each of the parts does not have a specific MIME. How can I compose my multipart/form-data with a different MIME for each parts in Javascript?

    POST /api/v1/inventory
    Host: localhost:8000
    Origin: http://localhost:9000
    Content-Type: multipart/form-data; boundary=------border
    
    ------border
    Content-Disposition: form-data; name="owner"
    
    john doe
    ------border
    Content-Disposition: form-data; name="image"; filename="mybook.png"
    Content-Type: image/png
    
    
    ------border
    Content-Disposition: form-data; name="items"
    Content-Type: application/json
    
    {"name": "Book", "quantity": "12"}
    ------border--
    

    Relevant References:

    1. https://developer.mozilla.org/en-US/docs/Web/Guide/Using_FormData_Objects
    2. REST - HTTP Post Multipart with JSON
    3. http://code.activestate.com/recipes/578846-composing-a-postable-http-request-with-multipartfo/
    4. application/x-www-form-urlencoded or multipart/form-data?
    5. https://stackoverflow.com/a/9082243/764592

    Update

    Apologize for asking a wrong question. The original problem is that, I can see the server calling the logic something like,

    func POST(req):
         owner = req.owner // This is string
         image = req.image // This is file object
         itemQuantity = req.items.quantity // Items is an object with attribute quantity
         itemName = req.items.name // Items is an object with attribute name
    

    I have also managed to figure out how to submit such a post request. I will post my answer below.

    Once again sorry for asking a wrong question.

  • Yeo
    Yeo almost 10 years
    Although it set the Content-Type: 'application/json', it is still wrapped under a Blob. Hence, the API reject my request. gist.github.com/9gix/…
  • Ja͢ck
    Ja͢ck almost 10 years
    Ah, think I've made a typo; please try again.
  • Yeo
    Yeo almost 10 years
    The result of my previous comment is based on the proper syntax of Blob.
  • Ja͢ck
    Ja͢ck almost 10 years
    @Yeo Did you try formData.append('items', JSON.stringify(...)) as well?
  • Yeo
    Yeo almost 10 years
    because i'm using angular, therefore this is what I use to convert into JSON angular.toJson() Which I believe does the same things. docs.angularjs.org/api/ng/function/angular.toJson
  • Ja͢ck
    Ja͢ck almost 10 years
    @Yeo That should be the same; it seems that with FormData you either get to upload a file with a specific content type or just a string value (without specific content type).
  • iamserious
    iamserious over 9 years
    Ah, you saved my day. I also assumed json had to be in a single object like a regular post data! thanks.
  • Isthar
    Isthar about 7 years
    The fact of data being sent with filename="blob" is not a problem. This Blob wrapper is perfectly working for me, with Angular 2 as front end and Spring 4 as backend.
  • cabaji99
    cabaji99 about 6 years
    Yeap works with blob: var blob = new Blob([JSON.stringify(requestVo)], { type: "application/json" }) formData.append('data',blob);
  • Shawn
    Shawn almost 3 years
    Thank you! This was exactly what I needed!
  • José Mancharo
    José Mancharo over 2 years
    How do I make this work in node.js, reading a file from the file-system? Blob is not available on node.