Preferred method for downloading a file generated on the fly in Flask

10,023

Are you running the flask app behind a front end web server such as nginx or apache (which would be the best way to handle the downloading of files). If you're using nginx you can use the 'X-Accel-Redirect' header. For this example I'll use the directory /srv/static/reports as the directory you're creating the zipfiles in and wanting to serve them out of.

nginx.conf

in the server section

server {
  # add this to your current server config
  location /reports/ {
    internal;
    root /srv/static;
  }
}

your flask method

send the header to nginx to server

from flask import make_response
@app.route('/download', methods=['GET','POST'])
def download():
    error=None
    # ..
    if request.method == 'POST':
      if download_list == None or len(download_list) < 1:
          error = 'No files to download'
          return render_template('download.html', error=error, download_list=download_list)
      else:
          timestamp = dt.now().strftime('%Y%m%d:%H%M%S')
          zfname = 'reports-' + str(timestamp) + '.zip'
          zf = zipfile.ZipFile(downloaddir + zfname, 'a')
          for f in download_list:
              zf.write(downloaddir + f, f)
          zf.close()

          # TODO: remove zipped files, move zip to archive

          # tell nginx to server the file and where to find it
          response = make_response()
          response.headers['Cache-Control'] = 'no-cache'
          response.headers['Content-Type'] = 'application/zip'
          response.headers['X-Accel-Redirect'] = '/reports/' + zf.filename
          return response

If you're using apache, you can use their sendfile directive http://httpd.apache.org/docs/2.0/mod/core.html#enablesendfile

Share:
10,023

Related videos on Youtube

robots.jpg
Author by

robots.jpg

I develop web applications and data processing tools, and I am a Linux and VMware administrator.

Updated on October 12, 2020

Comments

  • robots.jpg
    robots.jpg over 3 years

    I have a page that displays a list of files in a directory. When the user clicks on the Download button, all of these files are zipped into a single file, which is then offered for download. I know how to send this file to the browser when the button is clicked, and I know how to reload the current page (or redirect to a different one), but is it possible to do both in the same step? Or would it make more sense to redirect to a different page with a download link?

    My download is initiated with the Flask API's send_from_directory. Relevant test code:

    @app.route('/download', methods=['GET','POST'])
    def download():
        error=None
        # ...
    
        if request.method == 'POST':
            if download_list == None or len(download_list) < 1:
                error = 'No files to download'
            else:
                timestamp = dt.now().strftime('%Y%m%d:%H%M%S')
                zfname = 'reports-' + str(timestamp) + '.zip'
                zf = zipfile.ZipFile(downloaddir + zfname, 'a')
                for f in download_list:
                    zf.write(downloaddir + f, f)
                zf.close()
    
                # TODO: remove zipped files, move zip to archive
    
                return send_from_directory(downloaddir, zfname, as_attachment=True)
    
        return render_template('download.html', error=error, download_list=download_list)
    

    Update: As a workaround, I am now loading a new page with the button click, which lets the user initiate the download (using send_from_directory) before returning to the updated listing.

  • robots.jpg
    robots.jpg about 13 years
    Thanks - the app will be running behind Apache, and I see that I should probably be using X-Sendfile to serve the files. The other part of my question was whether or not it's possible to render a template (to update the information shown) and return a file response in the same step. My understanding so far is that it's not.

Related