简体   繁体   中英

Force-Download with php on Amazon S3

I am trying to use http://code.google.com/p/amazon-s3-php-class/ to force-dowload files from AWS S3. I have an mp3 that I want people to "play" or "download." By default the when you access the file directly on s3 it begins to play in the browser. I need to add an option to actually download. I have Googled and found came up with nothing. I conceptually know what needs to happen but don't know how to produce it php. I know I need to modify the headers to Content-Disposition: attachment. Any help would be greatly appreciated.

Thanks, Michael

Amazon has now solved this problem and allows overriding of headers on a per-request basis with signed requests:

http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?RESTObjectGET.html

w00t!

The php scripts that have been mentioned so far will work ok, but the main downside is that every time a visitor on your site requests a file, your own servers will load it from the S3 and then relay that data to the browser. For low traffic sites, it's probably not a big deal, but for high traffic ones, you definitely want to avoid running everything through your own servers.

Luckily, there's a fairly straight-forward way to set your files to be forced to download from the S3. And you're exactly right - you just want to set the content-type and content-disposition (just setting content-disposition will work in some browsers, but setting both should work in all browsers).

This code is assuming that you're using the Amazon S3 PHP class from Undesigned:

<?php

// set S3 auth and get your bucket listing

// then loop through the bucket and copy each file over itself, replacing the "request headers":
S3::copyObject($bucketName, $filename, $bucketName, $filename, "public-read", array(), array("Content-Type" => "application/octet-stream", "Content-Disposition" => "attachment"));

?>

Now all your files will be forced to download. You may need to clear your cache to see the change. And obviously, don't do that on any file that you actually do want to be loaded "inline" in the browser.

The nice part with this solution is that applications that load media files directly (like let's say an mp3 player in Flash) don't care about the content-type or content-disposition, so you can still play your files in the browser and then link to download that same file. If the user already finished loading the file in flash, they'll most likely still have it in their cache, which means their download will be super quick and it won't even cost you any extra bandwidth charges from the S3.

Just wanting to post a contribution to this, Alex Neth is correct on this reference, but i do not feel a link is sufficient information, using amazon's own AWS PHP SDK2. Below I've outlined a basic (untested) method for calling data this way, you can use either S3 Factory Method or AWS Service Builder to make the S3 Client.

<?php
// S3 Factory Method
/*use Aws\S3\S3Client;

$s3= S3Client::factory(array(
    'key'    => '<aws access key>',
    'secret' => '<aws secret key>'
));*/

// OR AWS Service Builder
use Aws\Common\Aws;

// Create a service builder using a configuration file
$aws = Aws::factory('/path/to/my_config.json');

// Get the client from the builder by namespace
$s3 = $aws->get('S3');

// Now lets create our request object.
$command = $s3->getCommand('GetObject',array(
    'Bucket'                        => 'your-bucket-name',
    'Key'                           => 'keyname',
    'ResponseContentType'           => 'application/octet-stream',
    'ResponseContentDisposition'    => 'attachment; filename="filename.mp3',
));
$url = $command->createPresignedUrl('+1 days');
?>

You can then use PHP's header("Location: $url"); in order to redirect the visitor to the MP3 file with a force download, this should prevent it from playing in the browser, Please note, i use ResponseContentType quite frequently but I've never used ResponseContentDisposition with AWS (it should work according to the docs).

Converting this sample into a function should be easy, you could even pass in $bucket, $key, $force_download as such

<?php
use Aws\Common\Aws;

function gen_url($bucket,$key,$force_download=false){
    // OR AWS Service Builder (personal method to do this)

    // Create a service builder using a configuration file
    $aws = Aws::factory('/path/to/my_config.json');

    // Get the client from the builder by namespace
    $s3 = $aws->get('S3');
    $params = array(
        'Bucket'                        => $bucket,
        'Key'                           => 'keyname',
        'ResponseContentType'           => 'application/octet-stream',
        'ResponseContentDisposition'    => 'attachment; filename="filename.mp3',
    );

    if($force_download){
        $params['ResponseContentType'] = 'application/octet-stream';
        $params['ResponseContentDisposition'] = 'attachment; filename="'.basename($key).'"';
    }

    $command = $s3->getCommand('GetObject',$params);
    return $command->createPresignedUrl('+1 days');
}

// Location redirection to an MP3 force downlaod
header("Location: ".gen_url("recordings","my-file.mp3",true));
// Location redirection to a MP3 that lets the browser decide what to do.
header("Location: ".gen_url("recordings","my-file.mp3"));

?>

WARNING, if you haven't figured it out, this requires the AWS PHP SDK 2 currently (April 7th 2014) found here http://aws.amazon.com/sdkforphp/ This code is mostly pseudo code and may require some additional tweaking to actually make work as i'm referencing this from memory.

Just add this to your file's metadata on s3:

Content-Disposition: attachment; filename=FILENAME.EXT
Content-Type: application/octet-stream
<?php
    require_once __DIR__.'/vendor/autoload.php';
    use Aws\Common\Aws;

    function gen_url($bucket,$key,$force_download=false){
        // OR AWS Service Builder (personal method to do this)


        $config = array(
                'key'    => '',
                'secret' => '',
        );

        // Create a service builder using a configuration file
        $aws = Aws::factory($config);

        // Get the client from the builder by namespace
        $s3 = $aws->get('S3');
        $params = array(
            'Bucket'                        => $bucket,
            'Key'                           => $key,
            'ResponseContentType'           => 'application/octet-stream',
            'ResponseContentDisposition'    => 'attachment; filename="'.$key,
        );

        if($force_download){
            $params['ResponseContentType'] = 'application/octet-stream';
            $params['ResponseContentDisposition'] = 'attachment; filename="'.basename($key).'"';
        }

        $command = $s3->getCommand('GetObject',$params);
        return $command->createPresignedUrl('+1 days');
    }

    $bucket = '';
    $filename = '';


    $url = gen_url($bucket,$filename,true);

    echo "\n".$url."\n\n";

The code above works, you just need to install the S3 composer dependency and link in the autoload file, put your key/secret into config and then supply the bucket/filename.

Also worth mentioning is you are able to hard-set the headers for files in S3. For example, if you need to force-download a certain file you can set the appropriate headers for that file. Such as defaulting to stream, and either having a secondary file set to force-download or spend the bandwidth to use php/fputs and force-download via PHP.

So modify my example above to be like this


<?php

header('Content-Type: audio/mpeg');
header("Content-Disposition: attachment; filename={$_GET['file']};");

readfile("url to the file/{$_GET['file']}");

exit();

?>

Now you will want to put some validation in there so that you not giving the world access to every file you put on S3, but this should work.

If you are using a library like Tarzan AWS , you can add meta headers, that amazon will include when the file is retrieved. Check out the meta parameter in the update_object function here, for example: http://tarzan-aws.com/docs/2.0/files/s3-class-php.html#AmazonS3.update_object

This is now possible by overwriting the S3 headers using signed requests.

Request Parameters

There are times when you want to override certain response header values in a GET response. For example, you might override the Content-Disposition response header value in your GET request.

You can override values for a set of response headers using the query parameters listed in the following table.

response-content-type - Sets the Content-Type header of the response response-content-disposition - Sets the Content-Disposition header of the response.

Note

You must sign the request, either using an Authorization header or a pre-signed URL, when using these parameters. They cannot be used with an unsigned (anonymous) request.

So, you would set those headers to:

response-content-disposition: attachment; filename=FILENAME.EXT
response-content-type: application/octet-stream

Found answer here: https://stackoverflow.com/a/5903710/922522

php would just download the file to the server, not the client. remember, php doesn't do anything on the client, it just returns content (html, javascript, css, xml, whatever...)

[edit: added for clarity]: php can serve audio content, but you want to serve a web page and audio at the same time. To get that client behaviour, you have to get the client to request the file based on the web page's html or javascript.

So you have to get the client to download the file. For instance, have an iframe on the page with the url of the file on s3. Use css to make the iframe invisible. It should render the page and download and play the mp3.

Otherwise, look into using javascript to kick of a download when the page loads. I'm not sure if that's possible.

<?php
// PHP solution (for OP and others), works with public and private files
// Download url expires after 30 minutes (no experation after the download initiates, for large files)
// ***Forces client download***
$signed_url = $s3_client->getObjectUrl($s3_bucket_name, $s3_filename_key, '+30 minutes', array(
    'ResponseContentType' => 'application/octet-stream',
    'ResponseContentDisposition' => 'attachment; filename="your-file-name-here.mp4"'
));
redirect($signed_url);

I never tried Amazon's S3 hosting, but don't you have access to using .htaccess files there? Then you can set Content-Type and Content-Disposition for an entire directory with this entry:

<IfModule mod_headers.c>
    <FilesMatch "\.(mp3)$">
            ForceType audio/mpeg
            Header set Content-Disposition attachment
    </FilesMatch>
</IfModule>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM