Created
March 4, 2011 04:27
-
-
Save chrisbloom7/854168 to your computer and use it in GitHub Desktop.
Example of using mod_xsendfile to send a file as a download.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* In your Apache conf file, set the following to enable XSendfile: | |
* | |
<Directory "/"> | |
# This should be handled by the XSendFile module, but a bug in 0.11.1 prevented it from being set properly. | |
EnableSendfile on | |
XSendFile on | |
# Absolute path to where your download files are stored. No trailing slash! | |
XSendFilePath "C:\inetpub\apacheroot\mysite\download_files" | |
</Directory> | |
*/ | |
function join_paths($paths) | |
{ | |
if (!is_array($paths)) $paths = func_get_args(); | |
foreach ($paths as $i => $part) { | |
$part = trim(preg_replace('|[\\\\/]+|', DIRECTORY_SEPARATOR, trim($part)) , DIRECTORY_SEPARATOR); | |
if (!strlen($part)) unset($paths[$i]); | |
else $paths[$i] = $part; | |
} | |
return join(DIRECTORY_SEPARATOR, $paths); | |
} | |
function can_haz_xsendfile() | |
{ | |
// This will return false if PHP is not loaded as a module (i.e. uses cgi) | |
return in_array('mod_xsendfile', apache_get_modules()); | |
} | |
function send_download_package_file($base_path, $file_path) | |
{ | |
$realpath = join_paths(DOWNLOAD_PACKAGE_BASE_ROOT, $base_path, $file_path); | |
if (file_exists($realpath)) { | |
// Fetching File | |
$mtime = ($mtime = filemtime($realpath)) ? $mtime : gmtime(); | |
$size = intval(sprintf("%u", filesize($realpath))); | |
header("Content-type: application/force-download"); | |
header('Content-Type: application/octet-stream'); | |
if (strstr($_SERVER["HTTP_USER_AGENT"], "MSIE") != false) { | |
header("Content-Disposition: attachment; filename=" . urlencode(basename($file_path)) . '; modification-date="' . date('r', $mtime) . '";'); | |
} else { | |
header("Content-Disposition: attachment; filename=\"" . basename($file_path) . '"; modification-date="' . date('r', $mtime) . '";'); | |
} | |
if (can_haz_xsendfile()) { | |
// Sending file via mod_xsendfile | |
header("X-Sendfile: " . join_paths($base_path, $file_path)); | |
} else { | |
// Sending file directly via script | |
if (intval($size + 1) > return_bytes(ini_get('memory_limit')) && intval($size * 1.5) <= 1073741824) { //Not higher than 1GB | |
// Setting memory limit | |
ini_set('memory_limit', intval($size * 1.5)); | |
} | |
@apache_setenv('no-gzip', 1); | |
@ini_set('zlib.output_compression', 0); | |
header("Content-Length: " . $size); | |
// Set the time limit based on an average D/L speed of 50kb/sec | |
set_time_limit(min(7200, // No more than 120 minutes (this is really bad, but...) | |
($size > 0) ? intval($size / 51200) + 60 // 1 minute more than what it should take to D/L at 50kb/sec | |
: 1 // Minimum of 1 second in case size is found to be 0 | |
)); | |
$chunksize = 1 * (1024 * 1024); // how many megabytes to read at a time | |
if ($size > $chunksize) { | |
// Chunking file for download | |
$handle = fopen($realpath, 'rb'); | |
$buffer = ''; | |
while (!feof($handle)) { | |
$buffer = fread($handle, $chunksize); | |
echo $buffer; | |
ob_flush(); | |
flush(); | |
} | |
fclose($handle); | |
} else { | |
// Streaming whole file for download | |
readfile($realpath); | |
} | |
} | |
return true; | |
} else { | |
// File not found! Throw error here... | |
} | |
return false; | |
} | |
// Absolute path, same as what you set for the XSendFilePath directive | |
define('DOWNLOAD_PACKAGE_BASE_ROOT', 'C:\inetpub\apacheroot\mysite\download_files'); | |
// Any intermediate path between XSendFilePath and your file. It should have neither a leading nor trailing slash | |
define('DOWNLOAD_PACKAGE_DIR', 'pdfs'); | |
if (send_download_package_file(DOWNLOAD_PACKAGE_DIR, 'some_big_file.pdf')) { | |
// Exit successfully. We could just let the script exit | |
// normally at the bottom of the page, but then blank lines | |
// after the close of the script code would potentially cause | |
// problems after the file download. | |
exit; | |
} | |
// else, raise error... | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
function return_bytes($val) | |
{ | |
$val = trim($val); | |
$last = strtolower($val[strlen($val) - 1]); | |
switch ($last) { | |
// The 'G' modifier is available since PHP 5.1.0 | |
case 'g': | |
$val*= 1024; | |
case 'm': | |
$val*= 1024; | |
case 'k': | |
$val*= 1024; | |
} | |
return $val; | |
} | |
?> |
I added the return_bytes
function definition. You will obviously have to include the file first, or copy the function into your document.
As for gmtime, I really have no idea where that function comes from. Obviously my app never had to fall back on that function call or it would be throwing an error. I think you can just replace it with time()
instead.
Yes, I know, thank you.
I haven't tested the XSendFile module, yet, as it requires a specific installment to the server.
That'd be beyond the aim of my script to serve users as easiest as possible as an open source project.
I used the similar parts of the rest.
goldsky/FileDownload-R@929c6b1
For the set_time_limit(), I use 300 as the Apache's default value.
Thank you for sharing.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
gmtime() ?
return_bytes() ?