Скачивание файлов через php / размер более 2 Гб

Catalyst

Новичок
Скачивание файлов через php / размер более 2 Гб

Добрый вечер.
Есть код, который отдаёт файлы через php, для скрытия их реального пути.
Код был составлен не без помощи участников этого форума.
Работал отлично с файлами до 1,5 Гб, до тех пор пока не понадобилось отдавать файлы размером 2-3 Гб.

Проблема в следующем: при скачивании получаю битый файл.
В основном это видео-файлы.
Т.е. например файл 2Гб читает практически корректно, но содержит ошибки.
Файл размером 2,9 Гб - последняя четверть фильма вообще не воспроизводится.

Буду рад, если укажите на причину возможной ошибки.

Ниже приведен код, который используется для отдачи:

PHP:
<?php

function dl_file($filename, $razmer) 
{
	if (!file_exists($filename)) die('Файл не найден');

	$from 		= 0;

	$http_range = getenv("HTTP_RANGE");	
	
	if (!empty($http_range)) 
	{
		$range 	= substr($http_range, strpos($http_range, '=') + 1);
		$from 	= strtok($range, '-');
				
		header('HTTP/1.1 206 Partial Content');		
	} 
	else
	{
		header('HTTP/1.1 200 Ok');
	}

	header("Last-Modified: " . date("D, d M Y H:i:s ", filemtime($filename)). "GMT");		
	header("Accept-Ranges: bytes");
	header("Content-Length: " . ($razmer - $from));
	
	if (!empty($http_range)) 
	{
		//header($conrange);
		header("Content-Range: bytes " . $from . "-" . ($razmer - 1) . "/" . $razmer);
	}
	
	header("Connection: close");
	header("Content-Type: application/octet-stream");
	header('Content-Disposition: attachment; filename="' . basename($filename) . '";');
	
	$fp = fopen($filename, 'rb');
	
	if ($from) 
	{
		fseek($fp, $from);
	}	
	
	while(!feof($fp)) 
	{
		echo fread($fp, 4096);		
		flush();
	}
	
	fclose($fp);
}

$file_size = sprintf("%u", filesize('bird.avi'));

dl_file('bird.avi', $file_size);


?>
 

TutanXamoN

Новичок
Скорее +
PHP:
header("Content-Transfer-Encoding:binary");
-~{}~ 19.03.08 09:47:

А вообще да - не совпадает мд5 только если файл качать в несколько заходов.
 

fixxxer

К.О.
Партнер клуба
не тупим
http://ru2.php.net/filesize
читать

это раз

два, если даже и получится с sprintf %u, то дальшейние вычисления типа $razmer-$from все равно все покоцают

три, выкинуть эту хрень и юзать nginx+X-Accel-Redirect или lighttpd+X-Sendfile
 

akxxiv

Новичок
А почему при запросе на файл не создать симлинк и затем редиректнуть на него?
 

kvf77

Red Devil
Nginx умеет анализировать хеадеры и отдавать файл напрямую с диска скрывая от пользователя его настоящее местоположение - так что никаких PHP не надо будет. Копать в сторону:

header('X-Accel-Redirect: путь до файла');

Ваш скрипт проверяет авторизацию и так далее, а потом выдает Nginx такой вот хеадер, а Nginx отдает файл сам, не сообщая пользователю настоящий путь до файла.
 

Catalyst

Новичок
Всем огромное спасибо за участие, особенно fixxxer.
Проблема решена путём замены:
PHP:
$file_size = sprintf("%u", filesize('bird.avi'));
на
PHP:
$file_size = trim(`stat -f "%10z" bird.avi`);
100% работает только на FreeBSD, под Linux тоже есть соответствующие команды, например du, для вывода размера в байтах.
 

dimagolov

Новичок
Catalyst, если система 64 битная, то по идее проблем быть не должно и с целочисленной арифметикой
 

kain76

Новичок
Сейчас вот попробовал скрипт, а мне броузер содержимое файла напрямую кидает на экран, а не в закачку. Что где нужно настроить?
 

kain76

Новичок
смотрел. вот скрипт один в один как в начале топика

<?php

function dl_file($filename, $razmer)
{
if (!file_exists($filename)) die('Файл не найден);

$from = 0;

$http_range = getenv("HTTP_RANGE");

if (!empty($http_range))
{
$range = substr($http_range, strpos($http_range, '=') + 1);
$from = strtok($range, '-');

header('HTTP/1.1 206 Partial Content');
}
else
{
header('HTTP/1.1 200 Ok');
}

header("Last-Modified: " . date("D, d M Y H:i:s ", filemtime($filename)). "GMT");
header("Accept-Ranges: bytes");
header("Content-Length: " . ($razmer - $from));

if (!empty($http_range))
{
//header($conrange);
header("Content-Range: bytes " . $from . "-" . ($razmer - 1) . "/" . $razmer);
}

header("Connection: close");
header("Content-Type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($filename) . '";');

$fp = fopen($filename, 'rb');

if ($from)
{
fseek($fp, $from);
}

while(!feof($fp))
{
echo fread($fp, 4096);
flush();
}

fclose($fp);
}

$filename="PlugDoc.rar";
$file_size = sprintf("%u", filesize($filename));

dl_file($filename, $file_size);

?>

Сейчас он не выводит всё на экран, но и не качает файл. Испробовал на двух разных хостингах. Просто показывает окно загрузки, которое висит 2 минуты, а потом говорит, что соединение с узлом невозможно и поэтому невозможно скачать файл. Хотя файл он видит, ибо не ругается что не нашёл
 

TutanXamoN

Новичок
kain76
Сейчас мы должны сказать почему у тебя не работает не твой скрипт по столь скудному описанию проблемы?
 

kain76

Новичок
да видимо нет.. скорее всего я сам должен всё сделать , а потом спросить у вас почему это работает. вопрос снят.
 

Catalyst

Новичок
Я немного рано обрадовался, до конца не дотестировал результат работы кода. Файл всё равно бьётся.

Автор оригинала: fixxxer
два, если даже и получится с sprintf %u, то дальшейние вычисления типа $razmer-$from все равно все покоцают

три, выкинуть эту хрень и юзать nginx+X-Accel-Redirect или lighttpd+X-Sendfile
Попробовал на nginx+X-Accel-Redirect, работает отлично с докачкой.

Хочется узнать, можно ли узнать корректный размер файла отдавая в связке Apache+PHP при докачке?
Или если можно объясните причину, почему не будут корректны вычисления типа:
Автор оригинала: fixxxer
$razmer-$from
 

dimagolov

Новичок
Catalyst
сделай
PHP:
echo 'PHP_INT_SIZE='.PHP_INT_SIZE.' PHP_INT_MAX='.PHP_INT_MAX;
и все поймешь

-~{}~ 26.03.08 14:05:

да, раз система не 64-битная, то надо смотреть сюда:http://www.php.net/manual/en/ref.bc.php

-~{}~ 26.03.08 14:08:

а filesize, кстати, ничем не поможет если файлы больше 4-х гиг
 

Catalyst

Новичок
dimagolov, спасибо за участие и желание помочь, но я спрашивал почему возникает ошибка именно из-за этой строки:
Автор оригинала: fixxxer
если даже и получится с sprintf %u, то дальшейние вычисления типа $razmer-$from все равно все покоцают
Говорят, что думать чужой головой хорошо, а своей гораздо лучше, и те кто говорят - абсолютно правы.
Итак проблема была здесь:
PHP:
fseek($fp, $from); (смотри первый пост)
т.к. при превышении max int значения в размере 2147483647 функция fseek устанавливает смещение в открытом файле неизвестно куда, вернее известно но не туда куда нужно.

для решения проблемы просто нужно немного изменить конструкцию с работой fseek:
PHP:
if ($from) 
{
	if($from > 2000000000)
	{
		$removal = $razmer - $from;
		
		fseek($fp, -$removal, SEEK_END);			
	}
	else
	{
		fseek($fp, $from);	
	}		
}
Всем отписавшимся ещё раз большое спасибо за участие.
Форуму крепкого здоровья и процветания, отличный форум.
 

dimagolov

Новичок
Catalyst, проблема в том, что в 32 битных системах макс. целое число в php 2147483647. поэтому если возникает необходимость оперировать большими значениями, то получаются проблемы.

P.S. сначала я думал, в 32-битных системах отдать файл с докачкой размером больше 4 гига никак не удастся из за невозможности сделать fseek дальше чем на 2147483647 байт. но подумав немного понял, что это как раз не проблема, лишь бы 32-битный fseek правильно отработал....
 

kode

never knows best
Просто хинт на будущее, моё ИМХО что такие вещи надо реализовывать ввиду cgi скриптов на C например....
 

fixxxer

К.О.
Партнер клуба
Автор оригинала: Catalyst
dimagolov, спасибо за участие и желание помочь, но я спрашивал почему возникает ошибка именно из-за этой строки:

Говорят, что думать чужой головой хорошо, а своей гораздо лучше, и те кто говорят - абсолютно правы.
Итак проблема была здесь:
PHP:
fseek($fp, $from); (смотри первый пост)
ага я там наврал
щас проверил, в диапазоне до 1000000000000 вычисления прекрасно работают через float (дальше точности флоата не хватает)
а вот когда этот флоат кормится тому же fseek получается преобразование в int со всеми прелестями

-~{}~ 27.03.08 04:16:

kode
если отдавать статику по приведенной тут схеме модулем апача неоптимально то делать это через cgi это ваще маразм, объяснять почему?
 
Сверху