Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Downloading and saving data with fetch() from authenticated REST

I have a React app working with a REST backend built in Python and Flask. I'm downloading data from a database and saving it as a CSV file through the browser. I have this working. What I don't understand, however, is why I had to go beyond the sources I've been reading and mash stuff up to get this to work. Why haven't I found this outlined better?

Some say all I have to do is set the response header with mimetype and Content-Disposition: attachment; filename=something.csv:

  • In python using Flask, how can I write out an object for download?
  • http://code.stephenmorley.org/php/creating-downloadable-csv-files/
  • http://www.jtricks.com/bits/content_disposition.html

Yet, this, alone, was only working with plain links, and not with fetch() and authentication, so I had to go looking for ways to save client data to disk such as this:

  • Using HTML5/Javascript to generate and save a file

So, my question is either:

  • Why do I have to do it this way?, or
  • What am I missing -- what is the easier way?

It appears that the answer to the first question is that I can't modify request headers (to add the auth token) except through XHR type work. This appears to be answered (non-answered, really) here:

  • Adding custom HTTP headers using JavaScript

And, that for some reason, responses to XHR with Content-Disposition: attachment are meaningless. Is that true? Is there not a more intrinsic way to manage requests like this in modern browsers?

I feel like I don't understand this enough and that bugs me.

Anyhow, here is the working code I am looking to simplify, if possible:

JS (React):

// https://stackoverflow.com/a/18197511/680464
download(filename, text) {
    var pom = document.createElement('a');
    pom.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(text));
    pom.setAttribute('download', filename);

    if (document.createEvent) {
        var event = document.createEvent('MouseEvents');
        event.initEvent('click', true, true);
        pom.dispatchEvent(event);
    }
    else {
        pom.click();
    }
}
downloadReport(studyID) {
    fetch(`/api/admin/studies/${studyID}/csv`
    ,   {
            headers: {
                "Authorization": "Bearer " + this.props.authAPI.getToken()
            ,   "Accept": "text/csv"
            }
        }
    )
    .then(this.checkStatus.bind(this))
    .then((response) => response.text())
    .then((responseText) => this.download(`study${studyID}.csv`, responseText))
    .catch((error) => {
        console.error(this.props.location.pathname, error)
    })
}

Python (Flask):

@app.route("/api/admin/studies/<int:study_id>/csv", methods=["GET"])
@admin.login_required
def admin_get_csv(study_id):

    test = [("1","2","3"),("4","5","6")]
    def generate():
        for row in test:
            yield ",".join(row) + "\n"

    return Response(
                generate()
            ,   mimetype="text/csv"
            ,   headers={"Content-Disposition": "attachment; filename=study{0}.csv".format(study_id)}
            )
like image 683
juanitogan Avatar asked May 08 '16 01:05

juanitogan


1 Answers

In reference to this answer, you can use FileSaver or download.js libraries.

Example:

var saveAs = require('file-saver');

fetch('/download/urf/file', {
  headers: {
    'Content-Type': 'text/csv'
  },
  responseType: 'blob'
}).then(response => response.blob())
  .then(blob => saveAs(blob, 'test.csv'));
like image 86
Conrado Fonseca Avatar answered Sep 18 '22 06:09

Conrado Fonseca