Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sending JSON data along with file #449

Closed
bolemeus opened this issue Dec 11, 2014 · 15 comments
Closed

Sending JSON data along with file #449

bolemeus opened this issue Dec 11, 2014 · 15 comments

Comments

@bolemeus
Copy link

I'm trying to send a file with some metadata to a Spring Controller at the backend, but I'm not having much luck. I think I narrowed the problem down to the absence of a content-type to go along with the json-data.

The request send is something along the lines of...

------WebKitFormBoundarywdhA8TIFsWhbSoX9
Content-Disposition: form-data; name="meta-data"

{"metadata":{...}}
------WebKitFormBoundarywdhA8TIFsWhbSoX9
Content-Disposition: form-data; name="file"; filename="2014-03-invoice.pdf"
Content-Type: application/pdf

In order for my Spring controller to recognize the meta-data as JSON (to map it to an object defined in my backend) the content type needs to be set to application/json, as shown in these docs... http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-multipart-forms-non-browsers

I've tried using the formDataAppender but I'm either not using it right, or it isn't possible to accomplish this with it.

My Spring controller (for completeness' sake)

    @RequestMapping(value = "/processwithfiles/", method = RequestMethod.POST)
    public void processWithFiles(@RequestPart("meta-data") final Document document,
            @RequestPart("file") final MultipartFile[] files, final HttpServletResponse response) {
        ...
    }

This throws an HttpMediaTypeNotSupportedException "Content type 'application/octet-stream' not supported"

I've noticed that when I use the annotation @RequestParam instead, it works, but only if I change the type of the meta-data to String. That would require me to parse this string to a JSON object myself.

Is there a way make sure the request looks like the one in the example...
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-multipart-forms-non-browsers

@danialfarid
Copy link
Owner

You cannot set the type of individual parts of multipart form data request, so it will be received as a string and need to be parsed back to json.
The cleaner way is to upload the file in one service call and return an id to the client then send that id along with metadata in a separate normal post call and then on the server relate the file and metadata using that id.

@bolemeus
Copy link
Author

Hello Danial,

Sending the metadata in a seperate call requires a lot more administration in my backend, since a user can upload multiple files at once. Also, because it is one action performed by the user, I really don't like to split it up into seperate calls to my backend. It's essentially one transaction. If the user uploads files and it's split up into two calls, and the second call fails for some reason, the files will have already been uploaded and will never be used. I could think of another fancy solution to hold the files while the backend is waiting for the second call, but this requires more boilerplate code, which needs to be maintained, etc...

I've already made another workaround for this, with a single call... The extra code required on my backend is minimal.

    @RequestMapping(value = "/processwithfiles/", method = RequestMethod.POST)
    public void processWithFiles(@RequestParam("meta-data") final String metaData,
            @RequestParam("file") final MultipartFile[] files)
            throws JsonParseException, JsonMappingException, IOException {
        MetaData document = objectMapper.readValue(metaData, MetaData.class);

        // Do other stuff
    }

This does work in almost thesame way as I intended before. Because the metaData now is a string, it's not required for the Content-Type to be set. The Objectmapper can then parse the JSON-string to an object.

@danialfarid
Copy link
Owner

Yep that would work.

Usually upload is a slower process is more prone to io errors so that's why I am suggesting to upload the file as soon as the user selects it in a tmp folder and return id, like the way gmail attachment works. This way you are sure that the files are on the server and user is good to go to submit the form with metadata info in a split second post call. I know it is more work on the backend but personally I think it is better user experience.

@7stud
Copy link

7stud commented Dec 17, 2014

You cannot set the type of individual parts of multipart form data request...

According to http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2, you can:

A "multipart/form-data" message contains a series of parts, each representing a successful control. The parts are sent to the processing agent in the same order the corresponding controls appear in the document stream. Part boundaries should not occur in any of the data; how this is done lies outside the scope of this specification.

As with all multipart MIME types, each part has an optional "Content-Type" header that defaults to "text/plain".

Or are you saying that with angular-file-upload, you cannot set each part's Content-Type?

@bolemeus
Copy link
Author

If the content-type of the JSON string could be set to 'application/json', it would make my code on the backend a lot more readable and maintainable.
I cant'speak for other frameworks, but for Spring MVC it is a pain to get it working right, right now.

@danialfarid
Copy link
Owner

XMLHttpRequest cannot set that.

@danialfarid
Copy link
Owner

In version 3.0.0 you can set the option sendObjectsAsJsonBlob to send the data or field object as a json blob with concent-type application/json in the form data.

@bgreeley
Copy link

I'm having trouble getting this to work. I'm assuming that sendObjectsAsJsonBlob turned into sendFieldsAs. Is that correct? Is there a working example somewhere?

@danialfarid
Copy link
Owner

@bgreeley yes that's the equivalent of that option. sendFieldsAs: json-blob

@bgreeley
Copy link

Thanks, @danialfarid. I can't seem to get the content type to set correctly though. I'm trying to it set to application/json. Is there some example code somewhere I could see to see where I'm going wrong?

@danialfarid
Copy link
Owner

@bgreeley http://jsfiddle.net/danialfarid/tqgr7pur/ if you send the file with username you will see in the network tab that the username is being sent with content-type application/json.

@bgreeley
Copy link

Thanks, @danialfarid. I ran my code again with the following config:

fields: {
    data: {
        caption: 'caption!',
        tags: ['abc', 'def']
    }
},
sendFieldsAs: 'json-blob'

I'm getting the following output:

------WebKitFormBoundaryA
Content-Disposition: form-data; name="data"; filename="blob"
Content-Type: application/json
------WebKitFormBoundaryA

And if have change sendFieldsAs to be "json", I see the following output:

------WebKitFormBoundaryB
Content-Disposition: form-data; name="data"
{"caption":"caption!","tags":["abc","def"]}
------WebKitFormBoundaryB

Is there a reason why the content type couldn't be set to "application/json" in this case? Is this maybe a bug with the library or is this a limitation of FormData? I read through an article on the MDN and saw that FormData could either by a File, Blob, or string. It didn't see any mention of limitations or guidelines around setting content types. Just for reference, I'll throw the link in here:

https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects

@danialfarid
Copy link
Owner

only blob can have type

@raghala
Copy link

raghala commented Jan 20, 2016

Hi Danial,

I am using this service for to file upload, All the things going well, but when you pass korean data as input it is converting to some double byte characters.

Could you please help me? Even you can test in http://embed.plnkr.co/mTyI676rSKh8WnjJoDQ0/
In the place username please give 안녕.

@danialfarid
Copy link
Owner

@raghala That's just the server response. if you see the network tab you will see that the data is being sent correctly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants