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

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?
LVL 32
DrDamnitAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Ray PaseurCommented:
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

0
DrDamnitAuthor 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.
0
Ray PaseurCommented:
Can you please give me the URL of one of the files?  I'll try some ideas.
0
Cloud Class® Course: MCSA MCSE Windows Server 2012

This course teaches how to install and configure Windows Server 2012 R2.  It is the first step on your path to becoming a Microsoft Certified Solutions Expert (MCSE).

DrDamnitAuthor 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

0
GaryCommented:
Wouldn't this be easier in javascript
Pity the download attribute isn't supported in IE else that would be the easiest route.
0
DrDamnitAuthor Commented:
I am open to doing it in Javascript. What are your thoughts Gary?
0
DrDamnitAuthor 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!"
0
GaryCommented:
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>
0
DrDamnitAuthor 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.
0
GaryCommented:
Ermm how about
https://github.com/dcneiner/Downloadify

I would assume this is not gonna happen on tablets/mobiles?
0
DrDamnitAuthor 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.
0
DrDamnitAuthor 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.
0
Ray PaseurCommented:
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

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
GaryCommented:
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.
0
DrDamnitAuthor 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.
0
GaryCommented:
I thought you wanted to serve the file directly and not be downloaded by your server first
0
Ray PaseurCommented:
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.
0
GaryCommented:
Yes you're right
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
PHP

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.