Skip to content

Instantly share code, notes, and snippets.

@chrisbloom7
Created March 4, 2011 04:27
Show Gist options
  • Save chrisbloom7/854168 to your computer and use it in GitHub Desktop.
Save chrisbloom7/854168 to your computer and use it in GitHub Desktop.
Example of using mod_xsendfile to send a file as a download.
<?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...
?>
<?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;
}
?>
@goldsky
Copy link

goldsky commented Sep 21, 2011

gmtime() ?
return_bytes() ?

@chrisbloom7
Copy link
Author

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.

@goldsky
Copy link

goldsky commented Sep 22, 2011

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