Я всегда использовал функцию становиться_файлом из библиотеки с открытым исходным кодом BalPHP , которую вы можете сразу подключить и воспроизвести (вставить) в свой проект. Это позволяет:
- Мгновенная загрузка файла
- Простое указание таких параметров, как тип контента, срок службы кэша, размер буфера.
- Поддерживает многоэтапные транзакции, что позволяет быстрее загружать файлы, которые не убивают ваш сервер при передаче больших файлов.
- Поддерживает функции паузы / возобновления.
- И etags для кеширования:
Вы можете найти последнюю версию здесь:
http://github.com/balupton/balphp/blob/master/trunk/lib/core/functions/_files.funcs.php#L75
А здесь он скопирован и вставлен по состоянию на 27 августа 2010 года:
/**
* Become a file download, should be the last script that runs in your program
*
* http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
*
* @version 3, July 18, 2009 (Added suport for data)
* @since 2, August 11, 2007
*
* @author Benjamin "balupton" Lupton <contact@balupton.com> - {@link http://www.balupton.com/}
*
* @param string $file_path
* @param string $content_type
* @param int $buffer_size
* @param string $file_name
* @param timestamp $file_time
*
* @return boolean true on success, false on error
*/
function become_file_download ( $file_path_or_data, $content_type = NULL, $buffer_size = null, $file_name = null, $file_time = null, $expires = null ) {
// Prepare
if ( empty($buffer_size) )
$buffer_size = 4096;
if ( empty($content_type) )
$content_type = 'application/force-download';
// Check if we are data
$file_descriptor = null;
if ( file_exists($file_path_or_data) && $file_descriptor = fopen($file_path_or_data, 'rb') ) {
// We could be a file
// Set some variables
$file_data = null;
$file_path = $file_path_or_data;
$file_name = $file_name ? $file_name : basename($file_path);
$file_size = filesize($file_path);
$file_time = filemtime($file_path);
$etag = md5($file_time . $file_name);
} elseif ( $file_name !== null ) {
// We are just data
$file_data = $file_path_or_data;
$file_path = null;
$file_size = strlen($file_data);
$etag = md5($file_data);
if ( $file_time === null )
$file_time = time();
else
$file_time = ensure_timestamp($file_time);
} else {
// We couldn't find the file
header('HTTP/1.1 404 Not Found');
return false;
}
// Prepare timestamps
$expires = ensure_timestamp($expires);
// Set some variables
$date = gmdate('D, d M Y H:i:s') . ' GMT';
$expires = gmdate('D, d M Y H:i:s', $expires) . ' GMT';
$last_modified = gmdate('D, d M Y H:i:s', $file_time) . ' GMT';
// Say we can go on forever
set_time_limit(0);
// Check relevance
$etag_relevant = !empty($_SERVER['HTTP_IF_NONE_MATCH']) && trim(stripslashes($_SERVER['HTTP_IF_NONE_MATCH']), '\'"') === $etag;
$date_relevant = !empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $file_time;
// Handle download
if ( $etag_relevant || $date_relevant ) {
// Not modified
header('HTTP/1.0 304 Not Modified');
header('Status: 304 Not Modified');
header('Pragma: public');
header('Cache-Control: private');
header('ETag: "' . $etag . '"');
header('Date: ' . $date);
header('Expires: ' . $expires);
header('Last-modified: ' . $last_modified);
return true;
} elseif ( !empty($_SERVER['HTTP_RANGE']) ) {
// Partial download
/*
* bytes=0-99,500-1499,4000-
*/
// Explode RANGE
list($size_unit,$ranges) = explode($_SERVER['HTTP_RANGE'], '=', 2);
// Explode RANGES
$ranges = explode(',', $ranges);
// Cycle through ranges
foreach ( $ranges as $range ) {
// We have a range
/*
* All bytes until the end of document, except for the first 500 bytes:
* Content-Range: bytes 500-1233/1234
*/
// Set range start
$range_start = null;
if ( !empty($range[0]) && is_numeric($range[0]) ) {
// The range has a start
$range_start = intval($range[0]);
} else {
$range_start = 0;
}
// Set range end
if ( !empty($range[1]) && is_numeric($range[1]) ) {
// The range has an end
$range_end = intval($range[1]);
} else {
$range_end = $file_size - 1;
}
// Set the range size
$range_size = $range_end - $range_start + 1;
// Set the headers
header('HTTP/1.1 206 Partial Content');
header('Pragma: public');
header('Cache-Control: private');
header('ETag: "' . $etag . '"');
header('Date: ' . $date);
header('Expires: ' . $expires);
header('Last-modified: ' . $last_modified);
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
header('Content-Range: bytes ' . $range_start . '-' . $range_end . '/' . $file_size);
header('Content-Length: ' . $range_size);
header('Content-Type: ' . $content_type);
if ( $content_type === 'application/force-download' )
header('Content-Disposition: attachment; filename=' . urlencode($file_name));
// Handle our data transfer
if ( !$file_path ) {
// We are using file_data
echo substr($file_data, $range_start, $range_end - $range_start);
} else {
// Seek to our location
fseek($file_descriptor, $range_start);
// Read the file
$remaining = $range_size;
while ( $remaining > 0 ) {
// 0-6 | buffer = 3 | remaining = 7
// 0,1,2 | buffer = 3 | remaining = 4
// 3,4,5 | buffer = 3 | remaining = 1
// 6 | buffer = 1 | remaining = 0
// Set buffer size
$buffer_size = min($buffer_size, $remaining);
// Output file contents
echo fread($file_descriptor, $buffer_size);
flush();
ob_flush();
// Update remaining
$remaining -= $buffer_size;
}
}
}
} else {
// Usual download
// header('Pragma: public');
// header('Cache-control: must-revalidate, post-check=0, pre-check=0');
// header('Expires: '. gmdate('D, d M Y H:i:s').' GMT');
// Set headers
header('HTTP/1.1 200 OK');
header('Pragma: public');
header('Cache-Control: private');
header('ETag: "' . $etag . '"');
header('Date: ' . $date);
header('Expires: ' . $expires);
header('Last-modified: ' . $last_modified);
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
header('Content-Length: ' . $file_size);
header('Content-Type: ' . $content_type);
if ( $content_type === 'application/force-download' )
header('Content-Disposition: attachment; filename=' . urlencode($file_name));
// Handle our data transfer
if ( !$file_path ) {
// We are using file_data
echo $file_data;
} else {
// Seek to our location
// Read the file
$file_descriptor = fopen($file_path, 'r');
while ( !feof($file_descriptor) ) {
// Output file contents
echo fread($file_descriptor, $buffer_size);
flush();
ob_flush();
}
}
}
// Close the file
if ( $file_descriptor )
fclose($file_descriptor);
// Done
return true;
}
Это также зависит от другой функции plug and play, которая называется sure_timestamp , которую вы можете найти здесь:
http://github.com/balupton/balphp/blob/master/trunk/lib/core/functions/_datetime.funcs.php#L31
/**
* Gets the days between two timestamps
* @version 1, January 28, 2010
* @param mixed $value
* @return timestamp
*/
function ensure_timestamp ( $value = null ) {
$result = null;
if ( $value === null ) $result = time();
elseif ( is_numeric($value) ) $result = $value;
elseif ( is_string($value) ) $result = strtotime($value);
else throw new Exception('Unknown timestamp type.');
return $result;
}