За последние 24 часа нас посетили 20740 программистов и 1130 роботов. Сейчас ищут 374 программиста ...

Скрипт скачивания с докачкой ( 206 Partial Content )

Тема в разделе "Прочие вопросы по PHP", создана пользователем AterCattus, 6 авг 2008.

  1. AterCattus

    AterCattus Активный пользователь

    С нами с:
    6 фев 2008
    Сообщения:
    80
    Симпатии:
    0
    Адрес:
    Санкт-Петербург
    Доброго времени суток. Возникла необходимость в скрипте скачивания файлов с сервера с поддержкой докачки (многопоточного скачивания). Перерыл кучу материалов, но все никак не получается. Основная причина в том - что клиенты просто не шлют "range" в заголовках запросов.

    Как я понял, первоначально запрос к серверу приходит не обязательно с range в заголовке. Начинается скачка файла моноблоком. Однако, если сервер вставил в заголовки ответа "Accept-Ranges: bytes", поддерживающий докачку клиент это видит и пробует подключаться еще параллельно + разрешает пользователю ставить закачку на паузу.

    Соответсвенно, когда на сервер приходит запрос с указанием интервала, клиенту отсылается уже только один блок.

    Но никак не получается добиться данной функциональности. Перепробовал кучу разнообразных заголовков и под разными клиентами. Какие-то игнорируют range и качают файл разом, какие-то понимают, какие-то вообще ругаются ( IE ;-) ).
    Объясните что я делаю не так :oops: . Заранее большое спасибо!

    PHP:
    1. <?php
    2.     set_time_limit( 0 );
    3.    
    4.     $filename = './kiwi.flv';
    5.    
    6.     define( 'MAX_SPEED', 1024 ); // 1КБ/сек
    7.    
    8.     $fsize = filesize( $filename );
    9.     $ftime = date( "D, d M Y H:i:s T", filemtime($filename) );
    10.     $fd = @fopen( $filename, "rb" );
    11.  
    12.     $partial = isset( $_SERVER["HTTP_RANGE"] );
    13.    
    14.    
    15.     // заголовок кода ответа
    16.     if ( $partial ) { header( 'HTTP/1.1 206 Partial Content', true, 206 ); header( 'Status: 206 Partial content' ); }
    17.     else            { header( 'HTTP/1.1 200 OK', true, 200 );  header( 'Status: 200 OK' ); }
    18.    
    19.     $isIE = ( isset( $_SERVER['HTTP_USER_AGENT'] ) && ( strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== FALSE ) );
    20.    
    21.     // общие заголовки
    22.     header( 'Content-Disposition: attachment; filename='.basename( $filename ) );
    23.     header( 'Accept-Ranges: bytes' );
    24.     header( 'Content-Transfer-Encoding: binary' );
    25.     header( "Last-Modified: $ftime" );
    26. //  header( 'Cache-Control: no-store, no-cache, must-revalidate' );
    27. //  header( 'Cache-Control: post-check=0, pre-check=0', false );
    28.     header( 'Pragma: no-cache' );
    29. //  header( 'Expires: 0' );
    30.     header( 'Content-Description: File Transfer' );
    31.     if ( $isIE )    header( 'Content-Type: application/force-download' );
    32.     else            header( 'Content-Type: application/octet-stream' );
    33.     // header('Connection: close');
    34.    
    35.     if ( !$partial ) { // Докачка не поддерживается клиентом
    36.         header( "Content-Length: $fsize" );
    37.         fpassthru( $fd );
    38.     }
    39.     else { // Докачка поддерживается клиентом
    40.        
    41.         // разбор диапазона и вычисление реальных значений
    42.         $range = $_SERVER["HTTP_RANGE"];
    43.         $range = explode( '=', $range );
    44.         if ( $range[0] != 'bytes' ) { $range_from = 0; $range_to = 0; }
    45.         else {
    46.             $range = $range[1];
    47.             $range = explode( '-', $range );
    48.             /*
    49.                 Если написано "200-299", то это 100 байт со смещения 200 от начала.
    50.                 А вот если написано "-500", то это последние 500 байт.
    51.             */
    52.             if ( !strlen( $range[0] ) ) { // "-500"
    53.                 $range_from = $fsize - (int)$range[1];
    54.                 $range_to = $fsize-1;
    55.             }
    56.             else { // "200-299" или "200-"
    57.                 $range_to = (int)$range[1];
    58.                 if ( !$range_to ) $range_to = $fsize-1;
    59.                 $range_from = (int)$range[0];
    60.                 if ( $range_from )
    61.                     fseek( $fd, $range_from );
    62.             }
    63.         }
    64.        
    65.         $size = $range_to - $range_from + 1;
    66.        
    67.         header( 'Content-Length: ' . $size );
    68.         header( 'Content-Range: bytes '.$range_from.'-'.$range_to.'/'.$fsize );
    69.        
    70.         $done = 0;
    71.         while ( !feof($fd) && !connection_status() && ( $done < $size ) ) {
    72.             echo fread( $fd, MAX_SPEED );
    73.             ob_flush();
    74.             flush();
    75.             $done += MAX_SPEED;
    76.             sleep( 1 );
    77.         }
    78.     }
    79.    
    80.     @fclose( $fd );
    81. ?>
     
  2. Anonymous

    Anonymous Guest

     
  3. AterCattus

    AterCattus Активный пользователь

    С нами с:
    6 фев 2008
    Сообщения:
    80
    Симпатии:
    0
    Адрес:
    Санкт-Петербург
    Да, действительно очепятка. Поправил в коде #1. Но, так как это ничего не изменило, вопрос открыт.

    Если интересно:
    IE7 качает махом (целиком сразу).
    FF2 - махом но его плагин DownThemAll! скачивает ~700КБ сразу (сервер локальный) а потом уже начинает как мне и нужно. Из интернета цифра уже реальная.
    DownloadMaster5 - махом.
     
  4. Anonymous

    Anonymous Guest

    AterCattus, ну мож они считают, что быстрее скачать целиком, чем плодить потоки. Попробуй на большрм файле, под гиг....
     
  5. AterCattus

    AterCattus Активный пользователь

    С нами с:
    6 фев 2008
    Сообщения:
    80
    Симпатии:
    0
    Адрес:
    Санкт-Петербург
    Гм... В FF и DM заработало. В IE вообще выдает окно Закачки "Получение сведений о файле..." и тихо мирно повисает. В чем может быть дело?
     
  6. Anonymous

    Anonymous Guest

    Перечитай еще раз. Ты точно это хотел спросить? )
     
  7. AterCattus

    AterCattus Активный пользователь

    С нами с:
    6 фев 2008
    Сообщения:
    80
    Симпатии:
    0
    Адрес:
    Санкт-Петербург
    Горбунов Олег, я убрал этот момент из вопросов - необходимость запрета кеширования отпала. Но вот с зависанием IE никак не могу разобраться. "Получение сведений о файле..." и все.

    PHP:
    1. <?php       $fsize = filesize( $filename );
    2.         $ftime = date( "D, d M Y H:i:s T", filemtime($filename) );
    3.        
    4.         $partial = isset( $_SERVER["HTTP_RANGE"] );
    5.        
    6.         // заголовок кода ответа
    7.         if ( $partial ) { header( 'HTTP/1.1 206 Partial Content', true, 206 ); header( 'Status: 206 Partial content' ); }
    8.         else            { header( 'HTTP/1.1 200 OK', true, 200 );  header( 'Status: 200 OK' ); }
    9.        
    10.         $isIE = ( isset( $_SERVER['HTTP_USER_AGENT'] ) && ( strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== FALSE ) );
    11.        
    12.         { // общие заголовки
    13.             header( 'Content-Disposition: attachment; filename='.basename( $filename ) );
    14.             header( 'Accept-Ranges: bytes' );
    15.             header( 'Content-Transfer-Encoding: binary' );
    16.            
    17.             header( "Last-Modified: $ftime" );
    18.             // header( 'Cache-Control: no-store, no-cache, must-revalidate' );
    19.             // header( 'Cache-Control: post-check=0, pre-check=0', false );
    20.             // header( 'Pragma: no-cache' );
    21.             // header( 'Expires: Thu, 01 Jan 1970 00:00:01 GMT' );
    22.            
    23.             header( 'Content-Description: File Transfer' );
    24.            
    25.             if ( $isIE )    header( 'Content-Type: application/force-download' );
    26.             else            header( 'Content-Type: application/octet-stream' );
    27.             // header('Connection: close');
    28.         }
    29.        
    30.         // если ограничение по скорости не задано или задано в 0, то выставляю 32КБ/сек
    31.         $max_speed = defined( 'MAX_SPEED' ) ? ( MAX_SPEED ? MAX_SPEED : 32*1024 ) : 32*1024;
    32.        
    33.         if ( !$partial ) { // Докачка не поддерживается клиентом
    34.             header( "Content-Length: $fsize" );
    35.            
    36.             // выдаю с учетом ограничений по скорости
    37.             while ( !feof($fd) && !connection_status() ) {
    38.                 echo fread( $fd, $max_speed );
    39.                 ob_flush(); flush();
    40.                 if defined( 'MAX_SPEED' ) sleep( 1 );
    41.             }
    42.         }
    43.         else { // Докачка поддерживается клиентом
    44.            
    45.             { // разбор диапазона и вычисление реальных значений
    46.                 $range = $_SERVER["HTTP_RANGE"];
    47.                 $range = explode( '=', $range );
    48.                 if ( $range[0] != 'bytes' ) { $range_from = 0; $range_to = 0; }
    49.                 else {
    50.                     $range = $range[1];
    51.                     $range = explode( '-', $range );
    52.  
    53.                     if ( !strlen( $range[0] ) ) { // "-500"
    54.                         $range_from = $fsize - (int)$range[1];
    55.                         $range_to = $fsize-1;
    56.                     }
    57.                     else { // "200-299" или "200-"
    58.                         $range_to = (int)$range[1];
    59.                         if ( !$range_to ) $range_to = $fsize-1;
    60.                         $range_from = (int)$range[0];
    61.                         if ( $range_from )
    62.                             fseek( $fd, $range_from );
    63.                     }
    64.                 }
    65.             }
    66.            
    67.             $size = $range_to - $range_from + 1;
    68.            
    69.             header( 'Content-Length: ' . $size );
    70.             header( 'Content-Range: bytes '.$range_from.'-'.$range_to.'/'.$fsize );
    71.            
    72.             // выдаю с учетом ограничений по скорости
    73.             $done = 0;
    74.             while ( !feof($fd) && !connection_status() && ( $done < $size ) ) {
    75.                 echo fread( $fd, $max_speed );
    76.                 ob_flush(); flush();
    77.                 if defined( 'MAX_SPEED' ) sleep( 1 );
    78.                 $done += MAX_SPEED;
    79.             }
    80.         }
    81.        
    82.         @fclose( $fd ); ?>
     
  8. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    Есть готовый
    PHP:
    1. <?
    2.  class download{
    3.  
    4.      var $properties = array(    'old_name' => "",
    5.                                  'new_name' => "",
    6.                                  'type' => "",
    7.                                  'size' => "",
    8.                                  'resume' => "",
    9.                                  'max_speed' => ""
    10.                                  );
    11.  
    12.      var $range = 0;
    13.  
    14.      function download($path, $name="", $resume=0, $max_speed=0){
    15.  
    16.          $name = ($name == "") ? substr(strrchr("/".$path,"/"),1) : $name;
    17.  
    18.          $file_size = @filesize($path);
    19.  
    20.          $this->properties =  array(
    21.                                      'old_name' => $path,
    22.                                      'new_name' => $name,
    23.                                      'type'=> "application/force-download",
    24.                                      'size' => $file_size,
    25.                                      'resume' => $resume,
    26.                                      'max_speed' => $max_speed
    27.                                      );
    28.  
    29.              if ($this->properties['resume']) {
    30.  
    31.                  if(isset($_SERVER['HTTP_RANGE'])) {
    32.  
    33.                      $this->range = $_SERVER['HTTP_RANGE'];
    34.                      $this->range = str_replace("bytes=", "", $this->range);
    35.                      $this->range = str_replace("-", "", $this->range);
    36.  
    37.                  } else {
    38.  
    39.                          $this->range = 0;
    40.  
    41.                  }
    42.  
    43.                  if ($this->range > $this->properties['size']) $this->range = 0;
    44.  
    45.              } else {
    46.  
    47.                  $this->range = 0;
    48.  
    49.              }
    50.  
    51.      }
    52.  
    53.  
    54.      function download_file(){
    55.  
    56.                  if ($this->range) {
    57.                      header($_SERVER['SERVER_PROTOCOL']." 206 Partial Content");
    58.                  } else {
    59.                      header($_SERVER['SERVER_PROTOCOL']." 200 OK");
    60.                  }
    61.              header("Pragma: public");
    62.              header("Expires: 0");
    63.              header("Cache-Control:");
    64.              header("Cache-Control: public");
    65.              header("Content-Description: File Transfer");
    66.              header("Content-Type: ".$this->properties["type"]);
    67.              header('Content-Disposition: attachment; filename="'.$this->properties["new_name"].'";');
    68.              header("Content-Transfer-Encoding: binary");
    69.  
    70.              if ($this->properties['resume']) header("Accept-Ranges: bytes");
    71.  
    72.              if ($this->range) {
    73.  
    74.              header("Content-Range: bytes {$this->range}-".($this->properties['size']-1)."/".$this->properties['size']);
    75.              header("Content-Length: ".($this->properties['size']-$this->range));
    76.  
    77.  
    78.              } else {
    79.  
    80.              header("Content-Length: ".$this->properties['size']);
    81.  
    82.              }
    83.  
    84.              @ini_set('max_execution_time', 0);
    85.              @set_time_limit();
    86.              $this->_download($this->properties["old_name"], $this->range);
    87.      }
    88.  
    89.      function _download ($filename, $range=0)
    90.      {
    91.          @ob_end_clean();
    92.  
    93.          if (($speed = $this->properties['max_speed']) > 0)
    94.              $sleep_time = (8 / $speed) * 1e6;
    95.          else
    96.              $sleep_time = 0;
    97.  
    98.          $handle = fopen($filename, 'rb');
    99.          fseek($handle,$range);
    100.  
    101.          if ($handle === false)
    102.          {
    103.              return false;
    104.          }
    105.  
    106.          while (!feof($handle))
    107.          {
    108.              print (fread($handle, 1024*8));
    109.              ob_flush();
    110.              flush();
    111.              usleep($sleep_time);
    112.          }
    113.  
    114.          fclose($handle);
    115.  
    116.          return true;
    117.      }
    118.  
    119.  }
    120.  
    121.  ?>
     
  9. AterCattus

    AterCattus Активный пользователь

    С нами с:
    6 фев 2008
    Сообщения:
    80
    Симпатии:
    0
    Адрес:
    Санкт-Петербург
    Mr.M.I.T.
    Так готовый каждый дурак взять может. Где же тогда развитие, если все на готовеньком. Так и до Delphi VCL деградировать можно ;-) Я самостоятельно написал код, получилось не идеально. Вот и прошу помочь разобраться что именно в нем не так.
    А то получается "- у меня не работает A. -да возьми Б и не парься!"
     
  10. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    Mr.M.I.T.
    Спасибо
     
  11. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    все работает прекрасно. Только вот непонятка: пока файл качается, то не ползается по ссылкам на этом сайте В ЭТОМ БРОУЗЕРЕ. Т.е. в это время в другом броузере можно лазить по ссылкам. Но если и там поставить файл на закачку - не будет возможности переходить по ссылкам в том броузере.


    Где косяк? Куда копать? Настройки сервера?
     
  12. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    Блин, нужна помощь. Не понимаю, почему не пашет навигация пока качается файл...
     
  13. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    прикрепите в новичках тему плс эту.
     
  14. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    igordata
    session_write_close перед отдачей файла сделай. Файл сессии то лочится эксклюзивной блокировкой.
     
  15. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    да я сделал уже все. давно это было. тему говорю прикрепите в новичка.
     
  16. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    прикрепите топик
     
  17. Ensiferum

    Ensiferum Активный пользователь

    С нами с:
    11 июл 2010
    Сообщения:
    1.292
    Симпатии:
    0
    Адрес:
    из секты поклонников Нео
    все залатали свой апач в связи уязвимостью заголовков на докачку?
     
  18. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    А кто-то еще пользуется апачем? :D
     
  19. Ensiferum

    Ensiferum Активный пользователь

    С нами с:
    11 июл 2010
    Сообщения:
    1.292
    Симпатии:
    0
    Адрес:
    из секты поклонников Нео
    вообще да ))
     
  20. inhell

    inhell Активный пользователь

    С нами с:
    28 ноя 2011
    Сообщения:
    1
    Симпатии:
    0
    ткните куда вставить, не пойму что-то. перед 108ой строкой или перед 114ой (из поста Mr.M.I.T.)
     
  21. ARACOOL

    ARACOOL Активный пользователь

    С нами с:
    10 ноя 2006
    Сообщения:
    52
    Симпатии:
    0
    Адрес:
    Самарканд
    Вы тестировали это на мобильных браузерах?
     
  22. krow7

    krow7 Активный пользователь

    С нами с:
    12 авг 2009
    Сообщения:
    398
    Симпатии:
    0
    Адрес:
    из Азии
    Посоны, а полный ваш вариант с фиксами где можно почитать? Интересно стало :)
     
  23. HCI

    HCI Активный пользователь

    С нами с:
    22 мар 2013
    Сообщения:
    7
    Симпатии:
    0
    Спасибо большое! Давно искал :)
     
  24. cminvest

    cminvest Новичок

    С нами с:
    29 дек 2013
    Сообщения:
    2
    Симпатии:
    0
    Адрес:
    Пермь
    нормально
     
  25. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    Вы что-то хотели сказать? В чем смысл вашего сообщения?