[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
?
Solved

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

Posted on 2014-08-25
18
Medium Priority
?
201 Views
Last Modified: 2014-08-25
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?
0
Comment
Question by:DrDamnit
  • 8
  • 6
  • 4
18 Comments
 
LVL 111

Expert Comment

by:Ray Paseur
ID: 40283694
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
 
LVL 32

Author Comment

by:DrDamnit
ID: 40283701
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
 
LVL 111

Expert Comment

by:Ray Paseur
ID: 40283722
Can you please give me the URL of one of the files?  I'll try some ideas.
0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 32

Author Comment

by:DrDamnit
ID: 40283725
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
 
LVL 58

Expert Comment

by:Gary
ID: 40283740
Wouldn't this be easier in javascript
Pity the download attribute isn't supported in IE else that would be the easiest route.
0
 
LVL 32

Author Comment

by:DrDamnit
ID: 40283745
I am open to doing it in Javascript. What are your thoughts Gary?
0
 
LVL 32

Author Comment

by:DrDamnit
ID: 40283749
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
 
LVL 58

Expert Comment

by:Gary
ID: 40283753
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
 
LVL 32

Author Comment

by:DrDamnit
ID: 40283765
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
 
LVL 58

Expert Comment

by:Gary
ID: 40283767
Ermm how about
https://github.com/dcneiner/Downloadify

I would assume this is not gonna happen on tablets/mobiles?
0
 
LVL 32

Author Comment

by:DrDamnit
ID: 40283771
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
 
LVL 32

Author Comment

by:DrDamnit
ID: 40283775
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
 
LVL 111

Accepted Solution

by:
Ray Paseur earned 2000 total points
ID: 40283777
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
 
LVL 58

Expert Comment

by:Gary
ID: 40283847
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
 
LVL 32

Author Closing Comment

by:DrDamnit
ID: 40283891
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
 
LVL 58

Expert Comment

by:Gary
ID: 40283903
I thought you wanted to serve the file directly and not be downloaded by your server first
0
 
LVL 111

Expert Comment

by:Ray Paseur
ID: 40283949
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
 
LVL 58

Expert Comment

by:Gary
ID: 40283968
Yes you're right
0

Featured Post

What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction This article is intended for those who are new to PHP error handling (https://www.experts-exchange.com/articles/11769/And-by-the-way-I-am-New-to-PHP.html).  It addresses one of the most common problems that plague beginning PHP develop…
There are times when I have encountered the need to decompress a response from a PHP request. This is how it's done, but you must have control of the request and you can set the Accept-Encoding header.
Explain concepts important to validation of email addresses with regular expressions. Applies to most languages/tools that uses regular expressions. Consider email address RFCs: Look at HTML5 form input element (with type=email) regex pattern: T…
The viewer will learn how to dynamically set the form action using jQuery.
Suggested Courses
Course of the Month20 days, 7 hours left to enroll

867 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question