6

How can I return a JSON response and a file response:

Right now I do this:

runNumber = "A0001"
response = None
try:
    response = make_response("Line One\r\nLine Two\r\n")
    response.headers["Content-Disposition"] = "attachment; filename=" + runNumber + ".txt"
except MyCustomException as e:
    response = jsonify(error=e.value, runnumber=runNumber)
except:
    raise
return(response)

But that only allows me to return JSON OR a File. In some cases, I want to return both.

[edit:] The case where I want to return JSON and a file is when there is a warning about the file contents that the user should check before using the file.

If this is not possible, I will add the warning to the contents of the file.

Be Kind To New Users
  • 9,672
  • 13
  • 78
  • 125
  • 1
    And how would the client separate the file and JSON parts out again? – Martijn Pieters May 30 '15 at 15:50
  • 1
    That's simply not possible in HTTP - your response has one `Content-Type` (and one `Content-Disposition` for that matter). And multipart responses simply aren't supported in any standardized way by browsers ([1](http://stackoverflow.com/questions/10729733/is-there-a-de-facto-or-established-reason-why-multipart-http-responses-arent-ge), [2](http://stackoverflow.com/questions/1806228/browser-support-of-multipart-responses)) – Lukas Graf May 30 '15 at 15:50

2 Answers2

11

You cannot just return two responses. You get to return just the one.

That means that if you really need to return both JSON and a file you need to come up with a scheme that lets you return the two in one response and let the client separate out the file and JSON parts again.

There is no standard for this. Whatever you come up with will need to be carefully documented for your clients to handle explicitly.

You could use a custom header to store the JSON data, for example:

response = make_response("Line One\r\nLine Two\r\n")
response.headers["Content-Disposition"] = "attachment; filename=" + runNumber + ".txt"
response.headers['X-Extra-Info-JSON'] = json.dumps(some_object)

Or you could put the file contents in the JSON data. JSON isn't the greatest format for binary data, you may want to encode the binary data to Base64 first:

filedata = "Line One\r\nLine Two\r\n".encode('base64')
return jsonify(name=runNumber + '.txt', data=filedata)

Or you could create a multipart MIME document, in the same way that a POST multipart/form-data body works.

What you pick depends on your use-cases (what kind of clients are using your API) and the size of the data (megabytes of file data in a JSON response is not very workable).

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
2

I had a similar issue with sending both an audio file and its transcript. Unfortunately, we cannot send two responses for a single API request. Also, making two API calls for a single task did not make sense. My workaround was to pass the binary data of the file encoded as a string through JSON and decode it in the frontend. I used python and flask in backend and react-js in frontend. Here is a snippet of the code. You have to modify the decoding section based on the file you are sending.

Python:

import base64
response = {
    "audioContent": base64.b64encode(binaryData).decode(),
    "botText": botText
}
return json.dumps(response)

JS:

fetch(URL, requestData).then((response) => response.json()).then((data) => {
    var binary_string = window.atob(data.audioContent);
    var len = binary_string.length;
    var bytes = new Uint8Array(len);
    for (var i = 0; i < len; i++) {
      bytes[i] = binary_string.charCodeAt(i);
    }
    // console.log("Binary Data: ", binaryData);
    var url = window.URL.createObjectURL(
      new Blob([bytes.buffer], { type: "audio/mp3" })
    );
    console.log("URL: ", url);
    var a = new Audio(url);
    a.play();
}

There are two points to remember:

  1. This works best when the size of the file being sent is small. Large files will generally affect your API responses
  2. The decoding of the file in frontend is quite slow in my case. I did not get much time to improve performance, but it did the task for me then.