1

I have a react client that takes in user input as a file and sends it to my remote Flask server for storage. I send the file in the form of a Werkzeug FileStorage object and in the remote server I store it with file.save(path). In the react client I'm trying to build a way to download the file from the server, however I'm running into problems. Currently my program is working for downloading .txt files. I'm able to do this though a fetch javascript request:

 fetch(FETCH_URL, {
  method: 'POST',
  body: data,
  headers: {
    'Content-Type': 'application/json'
  }
}).then((response) => {
    var a = response.body.getReader();
    a.read().then(({ done, value }) => {
        saveAsFile(new TextDecoder("utf-8").decode(value), 'filename.txt');
      }
    );
});


function saveAsFile(text, filename) {
  const type = 'application/text'; // modify or get it from response
  const blob = new Blob([text], {type});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click(); 
}

Thanks to some help I got in this post: Download file in react client from flask remote server

I know this code is specifically made to work only with .txt files based on the type being passed in to Blob, but the front end is not the real problem.

The real problem is in my remote flask server, the following code is what is called in the flask server:

 with open(filename, 'r') as f:
     contents = f.read()
     return contents

I tried returning the file itself but the server gives an error:

"ValueError: I/O operation on closed file." 

So I decided to return the contents of the file as shown above.

The problem arises when I try to get a file for example "download.jpeg". Reading the file gives the following error:

"UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte" 

From what I understand Flask works exclusively with 'utf-8' and I assume this means the file in the server is on 'utf-8' encoded.

Does anyone have a suggestion or guidance on a solution or a workaround maybe a way to change the files encoding when I save it on the server or something else that could help me with what I'm trying to do?

bwad
  • 71
  • 3
  • 16
  • What's the file content-type in the server ? and how you're encoding it ? – Kamalakannan J Jul 25 '18 at 19:55
  • The content-type is image/jpeg. Not sure how I encoded it, just a file I downloaded from the internet and uploaded with javascript input. It was stored in a FileStorage object and I sent that to the server. How do I specify encoding? – bwad Jul 25 '18 at 20:07
  • 1
    While reading the response, if you're able to get the content-type, you can use it while creating the blob object. i.e pass the `type` param from response header instead of hard-coding it to `application/text` – Kamalakannan J Jul 25 '18 at 20:10
  • Yes that shouldn't be a problem I'm just unsure about how to deal with the encoding problem – bwad Jul 25 '18 at 20:12
  • 1
    Have you tried reading the file in binary mode with `open(filename, 'rb')`? – Learning is a mess Jul 25 '18 at 22:27
  • Yes I tried and it worked, I was able to send the contents of the file to my react client in binary, however now I don't know the proper decoding to use with TextDecoder – bwad Jul 26 '18 at 18:09

2 Answers2

0

Fetch's Response has blob() to convert the response directly to blob, so you don't have to read the stream, you don't have to find out it's content type or anything. Just try the below solution.

fetch(FETCH_URL, {
  method: 'POST',
  body: data,
  headers: {
    'Content-Type': 'application/json'
  }
}).then((response) => {
      response.blob().then((blob) => {
         saveBlob(blob, 'filename');
      });
});


function saveBlob(blob, filename) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click(); 
}
Kamalakannan J
  • 2,818
  • 3
  • 23
  • 51
  • The problem is in the Flask backed and the file encoding though, not in the client. Flask doesn't know how to read my file because of its encoding I believe. – bwad Jul 25 '18 at 21:10
0

Try this: make sure to install axios. Also you probably won't have to deal with content type like above said. Obviously changing the method type to POST and bring ur data in.

                axios(FETCH_URL, {
                    method: 'GET',
                    responseType: 'blob', // important
                }).then((response) => { //Creates an <a> tag hyperlink that links the excel sheet Blob object to a url for downloading.
                    const url = window.URL.createObjectURL(new Blob([response.data]));
                    const link = document.createElement('a');
                    link.href = url;
                    link.setAttribute('download', `${Date.now()}.xlsx`); //set the attribute of the <a> link tag to be downloadable when clicked and name the sheet based on the date and time right now.
                    document.body.appendChild(link);
                    link.click(); //programmatically click the link so the user doesn't have to
                    document.body.removeChild(link);
                    URL.revokeObjectURL(url); //important for optimization and preventing memory leak even though link element has already been removed.
                });