How to serve a rackspace file, but force "Save As..."?

DrDamnit
DrDamnit used Ask the Experts™
on
I have some files living at Rackspace. To avoid filename collision, they are all renamed to an md5 hash of microtime(true), and the original file name is saved in the database.

When a user is to download one of these files, I need to:
1. serve the file straight from rackspace (they are huge).
2. Have the file pre-renamed back to the original file name so that when the user downloads it, it is transparent.

I have tried this:
			/* Extract information from Rackspace */

			$headers=get_headers($file_path);
			$buffer = explode(":", $headers[4]);
			$content_length = trim($buffer[1]);
			$buffer = explode(":", $buffer[7]);
			$content_type = trim($buffer[1]);

			$resource = fopen($file_path,'rb');

			/* Resend to user */
			http_send_content_disposition($filename,true);
			http_send_content_type($content_type);
			// http_send_file($file_path);
			http_send_stream($resource);

Open in new window


but it comes back with "File not found". However, when I do header("Location: $file_path") I get the files perfectly. (They are just named the hash, which is not nice ot the user).

What am I missing here?
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Most Valuable Expert 2011
Top Expert 2016

Commented:
Dr. Dammit: I think you can force a file download with something like this script (at least it works for me).  Add your own logic at line 30 to create the file name you want the client to see.

<?php // demo/force_download.php
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('log_errors',     TRUE);


// DEMONSTRATE HOW TO CAUSE A FILE DOWNLOAD


// REQUIRED FOR USE WITH THE PHP date() FUNCTIONS
date_default_timezone_set('America/New_York');

// A FILE TO DOWNLOAD - THIS LINK COULD COME IN THE URL VIA $_GET, OR COULD BE GENERATED INSIDE THE SCRIPT
$url = "http://www.IcoNoun.com/demo/short_text_file.txt";

// THE USE CASE FOR THE FUNCTION
force_download($url);


// FUNCTION TO FORCE A DOWNLOAD FROM A FILE
function force_download($filename)
{
    // GET THE CONTENTS OF THE FILE
    $filedata = @file_get_contents($filename);

    // SUCCESS
    if ($filedata)
    {
        // GET A NAME FOR THE FILE
        $basename = basename($filename);

        // THESE HEADERS ARE USED ON ALL BROWSERS
        header("Content-Type: application-x/force-download");
        header("Content-Disposition: attachment; filename=$basename");
        header("Content-length: ".(string)(strlen($filedata)));
        header("Expires: ".gmdate("D, d M Y H:i:s", mktime(date("H")+2, date("i"), date("s"), date("m"), date("d"), date("Y")))." GMT");
        header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");

        // THIS HEADER MUST BE OMITTED FOR IE 6+
        if (FALSE === strpos($_SERVER["HTTP_USER_AGENT"], 'MSIE '))
        {
            header("Cache-Control: no-cache, must-revalidate");
        }

        // THIS IS THE LAST HEADER
        header("Pragma: no-cache");

        // FLUSH THE HEADERS TO THE BROWSER
        flush();

        // WRITE THE FILE
        echo $filedata;
    }

    // ERROR
    else
    {
        trigger_error("ERROR: UNABLE TO OPEN $filename", E_USER_ERROR);
    }
}

Open in new window

Most Valuable Expert 2012

Author

Commented:
Ray:

Here's the challenge: the file_get_contents works for files that are 8MB. I have some that are 800MB. that would cause a significant delay to transfer 800MB from Rackspace to the server, and then re-serve from there... that's what started the whole problem. The file sizes.
Most Valuable Expert 2011
Top Expert 2016

Commented:
Can you please give me the URL of one of the files?  I'll try some ideas.
Introduction to R

R is considered the predominant language for data scientist and statisticians. Learn how to use R for your own data science projects.

Most Valuable Expert 2012

Author

Commented:
little bit of progress. This is still a relay, but serves (at least) the first 15K of the file:
<?php

$file_path = 'https://fe5c6f1e4c8b84451ab6-b2d3efd6307726e0e9944246b7b38ca9.ssl.cf1.rackcdn.com/16079_293db4eed8f033b3cb3823421dbe2408.jpg';

			/* Extract information from Rackspace */

			$headers=get_headers($file_path);
			// die("<pre>".print_r($headers,true)."</pre>");
			$buffer = explode(":", $headers[4]);
			$content_length = trim($buffer[1]);
			$buffer = explode(":", $headers[7]);
			$content_type = trim($buffer[1]);

			$resource = fopen($file_path,'rb');

			$filename = "test-image.jpg";
			/* Resend to user */
			http_send_content_disposition($filename,false);
			http_send_content_type($content_type);
			// http_send_file($file_path);
			while($data = fread($resource,1024*1024))
			{
				http_send_data($data);
			}

			fclose($resource);
?>

Open in new window

Expert of the Year 2014
Top Expert 2014

Commented:
Wouldn't this be easier in javascript
Pity the download attribute isn't supported in IE else that would be the easiest route.
Most Valuable Expert 2012

Author

Commented:
I am open to doing it in Javascript. What are your thoughts Gary?
Most Valuable Expert 2012

Author

Commented:
Oh, and Gary, remember... the filename itself has to come from the DB. Remember: original name "MyPicture.jpg". Rackspace name: asldkfjpao8sdjfopi.jpg.

User cave man say: "When me download, me need pretty name!"
Expert of the Year 2014
Top Expert 2014

Commented:
Will have a little think...
Can you place any restrictions on the browser used?
If so (FF,Chrome,Opera) you could just use:

<a href="sdfswerweh23423847.zip" download="myfile.zip"></a>
Most Valuable Expert 2012

Author

Commented:
Some users have WinXP (and *shudder* old versions of IE). Although, Chrome, Firefox, and Safari (newest versions) are the officially supported browsers. And I guess the new IE.
Expert of the Year 2014
Top Expert 2014

Commented:
Ermm how about
https://github.com/dcneiner/Downloadify

I would assume this is not gonna happen on tablets/mobiles?
Most Valuable Expert 2012

Author

Commented:
My preference is to get this done using HTTP Headers. It seems so (ridiculously) simple to tell the browser: "Go get this, and here's the name for it"... there has to be something simple I am (we are) missing.
Most Valuable Expert 2012

Author

Commented:
Downloadify seems like a good solution, however, I am fixing an existing codebase, and to add that would be quite an undertaking considering the site is live.
Most Valuable Expert 2011
Top Expert 2016
Commented:
This worked for me.  Downloaded file (27mb) here: http://iconoun.com/demo/throwaway/drdammit.jpg however that image may be too big for the browser to display.  Both Firefox and Chrome choked on it.

<?php // demo/temp_drdammit.php
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('log_errors',     TRUE);


// DEMONSTRATE HOW TO CAUSE A FILE DOWNLOAD


// REQUIRED FOR USE WITH THE PHP date() FUNCTIONS
date_default_timezone_set('America/New_York');

// A FILE TO DOWNLOAD - THIS LINK COULD COME IN THE URL VIA $_GET, OR COULD BE GENERATED INSIDE THE SCRIPT
$url = 'https://fe5c6f1e4c8b84451ab6-b2d3efd6307726e0e9944246b7b38ca9.ssl.cf1.rackcdn.com/16079_293db4eed8f033b3cb3823421dbe2408.jpg';

// THE USE CASE FOR THE FUNCTION
force_download($url);


// FUNCTION TO FORCE A DOWNLOAD FROM A FILE
function force_download($url, $filename='drdammit.jpg')
{
    $hdr = get_headers($url, TRUE);
    $ctl = trim($hdr['Content-Length']);
    $fpr = fopen($url,'rb');

    // SUCCESS
    if ($fpr)
    {
        // THESE HEADERS ARE USED ON ALL BROWSERS
        header("Content-Type: application-x/force-download");
        header("Content-Disposition: attachment; filename=$filename");
        header("Content-length: $ctl");
        header("Expires: ".gmdate("D, d M Y H:i:s", mktime(date("H")+2, date("i"), date("s"), date("m"), date("d"), date("Y")))." GMT");
        header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");

        // THIS HEADER MUST BE OMITTED FOR IE 6+
        if (FALSE === strpos($_SERVER["HTTP_USER_AGENT"], 'MSIE '))
        {
            header("Cache-Control: no-cache, must-revalidate");
        }

        // THIS IS THE LAST HEADER
        header("Pragma: no-cache");

        // FLUSH THE HEADERS TO THE BROWSER
        flush();

        // WRITE THE FILE
        fpassthru($fpr);
    }

    // ERROR
    else
    {
        trigger_error("ERROR: UNABLE TO OPEN $url", E_USER_ERROR);
    }
}

Open in new window

Expert of the Year 2014
Top Expert 2014

Commented:
PHP is not going to be able to do this without downloading the file itself which negates the reason for an external server

I cannot find a way to do this in JS that will work cross browser, the only real alternative (if you cannot use the flash alternative as above) is to use the new HTML5 download attribute and try to limit downloading to the aforementioned browsers.
Most Valuable Expert 2012

Author

Commented:
Excellent. Drop in replacement for my problem. Thanks, Ray.

@GaryC123: I think that your solution is also excellent, and I may revisit it int he future. If I could use Javascript to force the save as dialog without having to change our existing page code too much, and still stream from Rackspace, that would have been ideal.
Expert of the Year 2014
Top Expert 2014

Commented:
I thought you wanted to serve the file directly and not be downloaded by your server first
Most Valuable Expert 2011
Top Expert 2016

Commented:
I don't think the script is downloading the file to the server where the PHP runs.  I think it's just passing it through to the output buffers, apparently in chunks.  When you add memory_get_peak_usage(TRUE) the PHP script only used 2,621,440 bytes for a 27Mb file.  And interestingly, it downloaded the file to my client machine in about 10 seconds, which was a fraction of the time it took me to upload the same file to my server.
Expert of the Year 2014
Top Expert 2014

Commented:
Yes you're right

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial