0

This might appear to be a question for noobs but I want to know exactly what code is required to start a download when a user clicks on the download button. Let's say I have this webpage and a form which contains filters (i.e selecting a date range) which the users sets and when he clicks on the download button a csv document is created on his hard drive. Which module must I communicate to for the download to start, for the file to have this extension and to contain X data. I heard that you must set some fields in the HTTP header but I'm not too sure how I should go about it.

EDIT: The file download now works but my file now contains all the html previously written on the page as well as the data to download. Here's a snippet of my code. I removed a lot of statements to facilitate readability

# Handler class which handles the page /Download 
class downloadsHandler(webapp2.RequestHandler):

def get(self):

    # write download page on the browser
    template = JINJA_ENVIRONMENT.get_template('/pages/download.html')
    self.response.write(template.render(template_values))

    # data to download
    buf = getLatestdata()

    # size of data
    size  = sys.getsizeof(buf)

    # set HTTP headers to notify server of download
    self.response.headers["Content-Type"] = "text/csv"
    self.response.headers["Cache-Control"] = "no-cache, must-revalidate"
    self.response.headers["Content-Disposition"] = "attachment; filename=kioskData.csv"
    self.response.headers["Content-Length"] = size
    self.response.headers["Content-Transfer-Encoding"] = "binary"

    # generate download
    self.response.write(buf)

How do I tell the browser only to include the data to download?

df611
  • 143
  • 11
  • See this: http://stackoverflow.com/questions/6520231/how-to-force-browser-to-download-file and this: http://stackoverflow.com/a/11090338/2611927 – Hardy Jul 19 '14 at 23:34

3 Answers3

1

You should have the download button point to a PHP file (if using PHP) that would have the following lines as headers

<?php
    header('Content-type: text/csv');
    header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
    header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
    header('Content-Disposition: attachment; filename="filename.csv"');
?>

Hope this helped!

Update: Adding an example CSV file generation on the fly

<?php
    echo "This is a CSV file generated by PHP code on the fly";
    // Let's print the column headers on first row
    echo "ID,Name,Type\r\n";
    echo "1,John,1\r\n";
    echo "2,Doe,2\r\n";

    header('Content-type: text/csv');
    header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
    header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
    header('Content-Disposition: attachment; filename="filename.csv"');
?>
  • Thanks. I'm using GAE(Google App Engine)and I'm developping in Python. But these are the HTTP header fields which I must always set correct? – df611 Jul 19 '14 at 23:46
  • Use no-cache to disabled caching the file, so everytime the file is generated it is fetched from the server. – Mohammad Aftab Uddin Jul 19 '14 at 23:50
  • This is a silly question. Are these header values in the GET request sent by the client or in the server response? – df611 Jul 20 '14 at 00:49
  • 1
    I believe the code is missing `readfile("filename.csv" );` Without `readfile` the download will be empty. ` – mseifert Jul 20 '14 at 01:17
  • Right, readfile("filename.csv"); is going to be necessary if you want to have an existing file for download and are not if you are having content output from PHP using echo – Mohammad Aftab Uddin Jul 20 '14 at 08:43
  • df611, they shouldn't be on any type of requests but on the PHP file itself, let me share a code sample on the reply above (updated) – Mohammad Aftab Uddin Jul 20 '14 at 08:45
1

I use the following to download and view files (where supported). This code supports renaming of the file on the user's device.

<?php

getFile("test.csv");

function getFile($file, $mode="download", $outfile=""){
    // File Exists?
    if (file_exists($file)){
        // Parse Info / Get Extension
        $fsize = filesize($file);
        $path_parts = pathinfo($file);
        $ext = strtolower($path_parts["extension"]);

        // Determine Content Type
        switch ($ext) {
            case "pdf": $ctype="application/pdf"; break;
            case "exe": $ctype="application/octet-stream"; break;
            case "zip": $ctype="application/zip"; break;
            case "doc": $ctype="application/msword"; break;
            case "xls": $ctype="application/vnd.ms-excel"; break;
            case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
            case "gif": $ctype="image/gif"; break;
            case "png": $ctype="image/png"; break;
            case "jpeg":
            case "jpg": $ctype="image/jpg"; break;
            case "jpg": $ctype="image/jpg"; break;
            case "csv": $ctype="text/csv"; break;
                    // video
            case "3gp": $ctype='video/3gpp'; break;
            case "3g2": $ctype='video/3g2'; break;
            case "avi": $ctype='video/avi'; break;
            case "mp4": $ctype='video/mp4'; break;
            case "ogv": $ctype='video/ogg'; break;
            case "asf": $ctype='video/asf'; break;
            case "mov": $ctype='video/quicktime'; break;            
            default: $ctype="application/force-download";
        }

        header("Pragma: public"); // required
        header("Expires: 0");
        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
        header("Cache-Control: private",false); // required for certain browsers
        header("Content-Type: $ctype");

        $outfile = ($outfile=="" ? basename($file) : basename($outfile));
        if ($mode == "view"){
            // View file
            header('Content-Disposition: inline; filename='.$outfile);
        } 
        else {
            // Download file
            header('Content-Disposition: attachment; filename='. $outfile);
        }

        header("Content-Transfer-Encoding: binary");
        header("Content-Length: ".$fsize);

        if (ob_get_length() > 0 ) {
                    ob_clean();
                    flush();
        }
        readfile( $file );
    } 
    else {
        echo('File Not Found: ' . $file);
    }
} 
?>
mseifert
  • 5,390
  • 9
  • 38
  • 100
  • How do I keep the same page that the user is currently on while generating the download. Do I have to redirect the user to a different page for the download to work since the http headers for this page will be different? – df611 Jul 20 '14 at 00:05
  • Actually, the page won't change. Once the headers are sent, the php processing stops. After your form is submitted, you can reload the page your on (or any php), but before echoing any content, and after verifying what needs to be verified (e.g. `$_POST` parameters), download the file. The user will be left with the original loaded page. – mseifert Jul 20 '14 at 00:56
  • You can read more [here](http://www.boutell.com/newfaq/creating/forcedownload.html). It should answer the question you posted in the other answer as well. – mseifert Jul 20 '14 at 01:14
  • Did you include the code which has `if (ob_get_length() > 0 ) {ob_clean();flush();}` This should clear the buffer and remove the HTML. From a flow standpoint, you should check `$_POST` or `$_GET` data and initiate the download before processing and outputting the page html. – mseifert Jul 24 '14 at 16:18
0

Ok. So everything works fine now. I had to clear the output stream containing the current page's html data

self.response.clear()

just before setting the headers. This generates a csv file which only contains the content of the buffer

df611
  • 143
  • 11