How to receive files in Javascript sent from Flask using send_file()

Viewed 5495

I am developing a web application using HTML + plain Javascript in the frontend, and Flask as backend. The application sends some ID to the server, the server should generate a report as PDF file and send it back to the client.

I am using Flask for the backend and I have created the following endpoint:

    @app.route("/download_pdf", methods=['POST'])
    def download_pdf():
        if request.method == 'POST':
            report_id = request.form['reportid']
            print(report_id) //Testing purposes.
            // do some stuff with report_id and generate a pdf file.
            return send_file('/home/user/report.pdf', mimetype='application/pdf', as_attachment=True)
// I already tried different values for mimetype and as_attachment=False

From the command line I can test the endpoint and I get the right file, and the server console prints the 123 report_id as expected:

curl --form reportid=123 http://localhost:5000/download_pdf >> output.pdf

For the frontend side I created a button that calls a Javascript function:

<button id=pdf_button onclick="generatePdf()"> PDF </button>

The Javascript function looks like this:

function generatePdf(){
    var report_list = document.getElementById("report_list")

    if (report_list.selectedIndex < 0){
        alert("Please, select a report.");
    }else{
        var req = new XMLHttpRequest();

        req.open("POST", "/download_pdf", true);
        req.responseType = "document";
        req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        req.onreadystatechange = function(){
            console.log(req.readyState)
            console.log(req.status)
            console.log(req.response)
            var link = document.createElement('a')
            link.href = req.response;
            link.download="report.pdf"
            link.click()

        }
        var selected_value = report_list.options[report_list.selectedIndex].value;
        var params="reportid="+selected_value;
        req.send(params);
    }

};

req.response is null in this case. However, the call to the endpoint has been done correctly, as the backend console prints the report_id as expected.

Already tried:

Lastly, the Firefox console shows these 6 messages after pressing the related button (please, observe the console.log() calls in the previous code):

2
0
null
4
0
null

It seems that the Javascript function has been called twice when the button is pressed.

My goal is to get the PDF downloaded. I don't know if what am I doing wrong; I'd thank any help on this.

2 Answers

Finally, I found what the problem was and I post this for the record. I thought it was unrelated, but the <button> calling the Javascript function was inside a <form>. I checked that the form was updated before the call to the endpoint finished, causing the call to finish prepaturely.

If somebody else needs this as example, a snipet of the final code is as follows:

HTML (both the select and button are not part of a <form>):

<select id="report_list" size=20> ... </select> ... <button id="pdf_button" onclick="generatePdf()"> PDF </button>

Javascript:

function generatePdf(){
    var report_list = document.getElementById("report_list");
    var req = XMLHttpRequest();
    var selected_value = report_list.options[report_list.selectedIndex].value;

    req.open("POST", "/reports/"+selected_value+"/pdf", true);
    req.responseType = "blob";
    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    req.onreadystatechange = function(){
    if (this.readyState == 4 && this.status == 200){
        var blob = new Blob([this.response], {type: "application/pdf"});
        var url = window.URL.createObjectURL(blob);
        var link = document.createElement('a');
        document.body.appendChild(link);
        link.style = "display: none";
        link.href = url;
        link.download = "report.pdf";
        link.click();

        setTimeout(() => {
        window.URL.revokeObjectURL(url);
        link.remove(); } , 100);
    }
    };

    req.send();
}

Flask:

@app.route("/reports/<id>/pdf", methods=['POST'])
def get_pdf(id):
    if request.method == 'POST':
        return send_file(get_pdf_path(id), mimetype='application/pdf')

I am not sure if this is the best or more elegant way to get this done, but so far it works for me.

Your ajax settings are wrong, they should be like these

req.open("POST", "/download_pdf", true);
req.responseType = "blob";

req.onreadystatechange = function() {
  console.log(req.readyState)
  console.log(req.status)
  const blob = new Blob([req.response]);
  const url = window.URL.createObjectURL(blob);

  const link = document.createElement('a')
  link.href = url
  link.download = "report.pdf"
  link.click()
}

The response type should be blob and when you get the response, parse it as a blob. After some time, remove the link

setTimeout(() => {
    window.URL.revokeObjectURL(url);
    link.remove();
}, 100);
Related