60

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.

Community
  • 1
  • 1
Yeo
  • 11,416
  • 6
  • 63
  • 90
  • The API is currently in used with the mobile apps. If the mobile apps could generate such request, I believe that web browser should be able to compose these request also. – Yeo Jul 02 '14 at 15:33
  • Reference 5. show the spec that we should be able to compose this kind of request. – Yeo Jul 03 '14 at 04:01
  • Not sure if I have to create a custom XMLHttpRequest from scratch for this? https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest – Yeo Jul 03 '14 at 04:48
  • Should this question be closed as it is a wrong question. – Yeo Jul 03 '14 at 16:38

3 Answers3

109

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.

Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
  • 2
    Although it set the `Content-Type: 'application/json'`, it is still wrapped under a Blob. Hence, the API reject my request. https://gist.github.com/9gix/a79a70f9b56ed252c00e#file-multipart-form-data-L6-L11 – Yeo Jul 02 '14 at 17:10
  • Ah, think I've made a typo; please try again. – Ja͢ck Jul 02 '14 at 22:46
  • 1
    The result of my previous comment is based on the proper syntax of Blob. – Yeo Jul 03 '14 at 03:05
  • 1
    @Yeo Did you try `formData.append('items', JSON.stringify(...))` as well? – Ja͢ck Jul 03 '14 at 03:28
  • 1
    because i'm using angular, therefore this is what I use to convert into JSON `angular.toJson()` Which I believe does the same things. https://docs.angularjs.org/api/ng/function/angular.toJson – Yeo Jul 03 '14 at 03:58
  • @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). – Ja͢ck Jul 03 '14 at 04:26
  • 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. – Isthar May 07 '17 at 11:22
  • 2
    Yeap works with blob: var blob = new Blob([JSON.stringify(requestVo)], { type: "application/json" }) formData.append('data',blob); – cabaji99 Mar 23 '18 at 22:05
  • 1
    Thank you! This was exactly what I needed! – Shawn Jul 16 '21 at 04:53
  • How do I make this work in node.js, reading a file from the file-system? Blob is not available on node. – José Mancharo Nov 02 '21 at 22:43
9

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--
Yeo
  • 11,416
  • 6
  • 63
  • 90
5

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;
}
N-ate
  • 6,051
  • 2
  • 40
  • 48