What if you are developing an application that requires a client to upload a file or an image and information about the image via RESTful API and you want the client to send data as JSON? The easy answer would be to perform two posts to REST API.
But is that even possible in a single request?
Again, a quick answer would be to send Base64 encoded file data in a JSON string.
However, in reality, you have several different options:
- The first option would be to encode your file in Base64, which will turn it into a long ASCII string. Pay attention – you’ll need to count in at least 30% increase in the file size. Another disadvantage is additional processing for encoding on the client’s side and decoding on the server’s side or both depending on API usage.
- The Second option would be to send the file first in a multipart/form-data format via POST or PUT, then return an ID that represents the stored file. In the second request, the client sends the meta-information with the ID, for the server to make a relation between the file and the meta-information.
- And as the third option, there is the reverse second option, to first send the meta-information and to return the ID of the stored meta-information. Again, we need the second request to send the file with the ID of meta-information, so the server can pair file and meta-information.
- Finally, the most elegant solution would be to use a single multipart/related request. Rfc2387 states: “The Multipart/Related content-type provides a common mechanism for representing objects that are aggregates of related MIME body parts.” In the case of REST API, related objects are meta-information and file.
If you decide to use Base64 encoding useful functions are btoa() and atob(). The first one, btoa(), is the acronym of Binary to ASCII and is used for encoding binary data to ASCII representation. The second one, atob(), the acronym of ASCII to Binary, will decode ASCII encoded binary string to its original binary representation.
The example could be something like this:
<input type="file" id="filePicker">
<script>
var handleFileSelect = function(evt) {
var files = evt.target.files;
var file = files[0];
if (files && file) {
var reader = new FileReader();
reader.onload = function(readerEvt) {
var base64String = btoa(readerEvt.target.result);
// add base64String encoded file to your JSON API request
};
reader.readAsBinaryString(file);
}
};
if (window.File && window.FileReader && window.FileList && window.Blob) {
document.getElementById('filePicker').addEventListener('change', handleFileSelect, false);
} else {
alert('The File APIs are not fully supported in this browser.');
}
</script>
I won’t dive into details regarding the second option and third option since these involve two requests one for JSON information and one for file, which is pretty straightforward. The only difference between these two is the sequence of requests.
The final option is sending multipart/related content type for multiple objects in one package. In our case, these are JSON and file objects. It would look like this:
POST /api_endpoint HTTP/1.1
Content-Type: multipart/related; boundary=PART
--PART
Content-Type: application/json
{
"someKey": "someValue"
}
--PART
Content-Type: image/jpeg
Content-Length: 435029
Content-Transfer-Encoding: binary
[DATA (435029 bytes)]
--PART
Keep in mind that .send() method of XMLHttpRequest encodes data into UTF-8. To avoid this you can convert data to an array and send it as ArrayBuffer, or you could use .sendAsBinary() method.