Доброго времени суток. Возникла необходимость в скрипте скачивания файлов с сервера с поддержкой докачки (многопоточного скачивания). Перерыл кучу материалов, но все никак не получается. Основная причина в том - что клиенты просто не шлют "range" в заголовках запросов. Как я понял, первоначально запрос к серверу приходит не обязательно с range в заголовке. Начинается скачка файла моноблоком. Однако, если сервер вставил в заголовки ответа "Accept-Ranges: bytes", поддерживающий докачку клиент это видит и пробует подключаться еще параллельно + разрешает пользователю ставить закачку на паузу. Соответсвенно, когда на сервер приходит запрос с указанием интервала, клиенту отсылается уже только один блок. Но никак не получается добиться данной функциональности. Перепробовал кучу разнообразных заголовков и под разными клиентами. Какие-то игнорируют range и качают файл разом, какие-то понимают, какие-то вообще ругаются ( IE ;-) ). Объясните что я делаю не так . Заранее большое спасибо! PHP: <?php set_time_limit( 0 ); $filename = './kiwi.flv'; define( 'MAX_SPEED', 1024 ); // 1КБ/сек $fsize = filesize( $filename ); $ftime = date( "D, d M Y H:i:s T", filemtime($filename) ); $fd = @fopen( $filename, "rb" ); $partial = isset( $_SERVER["HTTP_RANGE"] ); // заголовок кода ответа if ( $partial ) { header( 'HTTP/1.1 206 Partial Content', true, 206 ); header( 'Status: 206 Partial content' ); } else { header( 'HTTP/1.1 200 OK', true, 200 ); header( 'Status: 200 OK' ); } $isIE = ( isset( $_SERVER['HTTP_USER_AGENT'] ) && ( strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== FALSE ) ); // общие заголовки header( 'Content-Disposition: attachment; filename='.basename( $filename ) ); header( 'Accept-Ranges: bytes' ); header( 'Content-Transfer-Encoding: binary' ); header( "Last-Modified: $ftime" ); // header( 'Cache-Control: no-store, no-cache, must-revalidate' ); // header( 'Cache-Control: post-check=0, pre-check=0', false ); header( 'Pragma: no-cache' ); // header( 'Expires: 0' ); header( 'Content-Description: File Transfer' ); if ( $isIE ) header( 'Content-Type: application/force-download' ); else header( 'Content-Type: application/octet-stream' ); // header('Connection: close'); if ( !$partial ) { // Докачка не поддерживается клиентом header( "Content-Length: $fsize" ); fpassthru( $fd ); } else { // Докачка поддерживается клиентом // разбор диапазона и вычисление реальных значений $range = $_SERVER["HTTP_RANGE"]; $range = explode( '=', $range ); if ( $range[0] != 'bytes' ) { $range_from = 0; $range_to = 0; } else { $range = $range[1]; $range = explode( '-', $range ); /* Если написано "200-299", то это 100 байт со смещения 200 от начала. А вот если написано "-500", то это последние 500 байт. */ if ( !strlen( $range[0] ) ) { // "-500" $range_from = $fsize - (int)$range[1]; $range_to = $fsize-1; } else { // "200-299" или "200-" $range_to = (int)$range[1]; if ( !$range_to ) $range_to = $fsize-1; $range_from = (int)$range[0]; if ( $range_from ) fseek( $fd, $range_from ); } } $size = $range_to - $range_from + 1; header( 'Content-Length: ' . $size ); header( 'Content-Range: bytes '.$range_from.'-'.$range_to.'/'.$fsize ); $done = 0; while ( !feof($fd) && !connection_status() && ( $done < $size ) ) { echo fread( $fd, MAX_SPEED ); ob_flush(); flush(); $done += MAX_SPEED; sleep( 1 ); } } @fclose( $fd ); ?>
Да, действительно очепятка. Поправил в коде #1. Но, так как это ничего не изменило, вопрос открыт. Если интересно: IE7 качает махом (целиком сразу). FF2 - махом но его плагин DownThemAll! скачивает ~700КБ сразу (сервер локальный) а потом уже начинает как мне и нужно. Из интернета цифра уже реальная. DownloadMaster5 - махом.
AterCattus, ну мож они считают, что быстрее скачать целиком, чем плодить потоки. Попробуй на большрм файле, под гиг....
Гм... В FF и DM заработало. В IE вообще выдает окно Закачки "Получение сведений о файле..." и тихо мирно повисает. В чем может быть дело?
Горбунов Олег, я убрал этот момент из вопросов - необходимость запрета кеширования отпала. Но вот с зависанием IE никак не могу разобраться. "Получение сведений о файле..." и все. PHP: <?php $fsize = filesize( $filename ); $ftime = date( "D, d M Y H:i:s T", filemtime($filename) ); $partial = isset( $_SERVER["HTTP_RANGE"] ); // заголовок кода ответа if ( $partial ) { header( 'HTTP/1.1 206 Partial Content', true, 206 ); header( 'Status: 206 Partial content' ); } else { header( 'HTTP/1.1 200 OK', true, 200 ); header( 'Status: 200 OK' ); } $isIE = ( isset( $_SERVER['HTTP_USER_AGENT'] ) && ( strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== FALSE ) ); { // общие заголовки header( 'Content-Disposition: attachment; filename='.basename( $filename ) ); header( 'Accept-Ranges: bytes' ); header( 'Content-Transfer-Encoding: binary' ); header( "Last-Modified: $ftime" ); // header( 'Cache-Control: no-store, no-cache, must-revalidate' ); // header( 'Cache-Control: post-check=0, pre-check=0', false ); // header( 'Pragma: no-cache' ); // header( 'Expires: Thu, 01 Jan 1970 00:00:01 GMT' ); header( 'Content-Description: File Transfer' ); if ( $isIE ) header( 'Content-Type: application/force-download' ); else header( 'Content-Type: application/octet-stream' ); // header('Connection: close'); } // если ограничение по скорости не задано или задано в 0, то выставляю 32КБ/сек $max_speed = defined( 'MAX_SPEED' ) ? ( MAX_SPEED ? MAX_SPEED : 32*1024 ) : 32*1024; if ( !$partial ) { // Докачка не поддерживается клиентом header( "Content-Length: $fsize" ); // выдаю с учетом ограничений по скорости while ( !feof($fd) && !connection_status() ) { echo fread( $fd, $max_speed ); ob_flush(); flush(); if defined( 'MAX_SPEED' ) sleep( 1 ); } } else { // Докачка поддерживается клиентом { // разбор диапазона и вычисление реальных значений $range = $_SERVER["HTTP_RANGE"]; $range = explode( '=', $range ); if ( $range[0] != 'bytes' ) { $range_from = 0; $range_to = 0; } else { $range = $range[1]; $range = explode( '-', $range ); if ( !strlen( $range[0] ) ) { // "-500" $range_from = $fsize - (int)$range[1]; $range_to = $fsize-1; } else { // "200-299" или "200-" $range_to = (int)$range[1]; if ( !$range_to ) $range_to = $fsize-1; $range_from = (int)$range[0]; if ( $range_from ) fseek( $fd, $range_from ); } } } $size = $range_to - $range_from + 1; header( 'Content-Length: ' . $size ); header( 'Content-Range: bytes '.$range_from.'-'.$range_to.'/'.$fsize ); // выдаю с учетом ограничений по скорости $done = 0; while ( !feof($fd) && !connection_status() && ( $done < $size ) ) { echo fread( $fd, $max_speed ); ob_flush(); flush(); if defined( 'MAX_SPEED' ) sleep( 1 ); $done += MAX_SPEED; } } @fclose( $fd ); ?>
Есть готовый PHP: <? class download{ var $properties = array( 'old_name' => "", 'new_name' => "", 'type' => "", 'size' => "", 'resume' => "", 'max_speed' => "" ); var $range = 0; function download($path, $name="", $resume=0, $max_speed=0){ $name = ($name == "") ? substr(strrchr("/".$path,"/"),1) : $name; $file_size = @filesize($path); $this->properties = array( 'old_name' => $path, 'new_name' => $name, 'type'=> "application/force-download", 'size' => $file_size, 'resume' => $resume, 'max_speed' => $max_speed ); if ($this->properties['resume']) { if(isset($_SERVER['HTTP_RANGE'])) { $this->range = $_SERVER['HTTP_RANGE']; $this->range = str_replace("bytes=", "", $this->range); $this->range = str_replace("-", "", $this->range); } else { $this->range = 0; } if ($this->range > $this->properties['size']) $this->range = 0; } else { $this->range = 0; } } function download_file(){ if ($this->range) { header($_SERVER['SERVER_PROTOCOL']." 206 Partial Content"); } else { header($_SERVER['SERVER_PROTOCOL']." 200 OK"); } header("Pragma: public"); header("Expires: 0"); header("Cache-Control:"); header("Cache-Control: public"); header("Content-Description: File Transfer"); header("Content-Type: ".$this->properties["type"]); header('Content-Disposition: attachment; filename="'.$this->properties["new_name"].'";'); header("Content-Transfer-Encoding: binary"); if ($this->properties['resume']) header("Accept-Ranges: bytes"); if ($this->range) { header("Content-Range: bytes {$this->range}-".($this->properties['size']-1)."/".$this->properties['size']); header("Content-Length: ".($this->properties['size']-$this->range)); } else { header("Content-Length: ".$this->properties['size']); } @ini_set('max_execution_time', 0); @set_time_limit(); $this->_download($this->properties["old_name"], $this->range); } function _download ($filename, $range=0) { @ob_end_clean(); if (($speed = $this->properties['max_speed']) > 0) $sleep_time = (8 / $speed) * 1e6; else $sleep_time = 0; $handle = fopen($filename, 'rb'); fseek($handle,$range); if ($handle === false) { return false; } while (!feof($handle)) { print (fread($handle, 1024*8)); ob_flush(); flush(); usleep($sleep_time); } fclose($handle); return true; } } ?>
Mr.M.I.T. Так готовый каждый дурак взять может. Где же тогда развитие, если все на готовеньком. Так и до Delphi VCL деградировать можно ;-) Я самостоятельно написал код, получилось не идеально. Вот и прошу помочь разобраться что именно в нем не так. А то получается "- у меня не работает A. -да возьми Б и не парься!"
все работает прекрасно. Только вот непонятка: пока файл качается, то не ползается по ссылкам на этом сайте В ЭТОМ БРОУЗЕРЕ. Т.е. в это время в другом броузере можно лазить по ссылкам. Но если и там поставить файл на закачку - не будет возможности переходить по ссылкам в том броузере. Где косяк? Куда копать? Настройки сервера?
igordata session_write_close перед отдачей файла сделай. Файл сессии то лочится эксклюзивной блокировкой.