Розмір віддаленого файлу без завантаження файлу


Відповіді:


100

Щось про це знайшов тут :

Ось найкращий спосіб (який я знайшов) отримати розмір віддаленого файлу. Зверніть увагу, що запити HEAD не отримують фактичного тіла запиту, вони просто отримують заголовки. Отже, надсилання запиту HEAD до ресурсу розміром 100 МБ займе стільки ж часу, скільки запит HEAD до ресурсу розміром 1 КБ.

<?php
/**
 * Returns the size of a file without downloading it, or -1 if the file
 * size could not be determined.
 *
 * @param $url - The location of the remote file to download. Cannot
 * be null or empty.
 *
 * @return The size of the file referenced by $url, or -1 if the size
 * could not be determined.
 */
function curl_get_file_size( $url ) {
  // Assume failure.
  $result = -1;

  $curl = curl_init( $url );

  // Issue a HEAD request and follow any redirects.
  curl_setopt( $curl, CURLOPT_NOBODY, true );
  curl_setopt( $curl, CURLOPT_HEADER, true );
  curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
  curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, true );
  curl_setopt( $curl, CURLOPT_USERAGENT, get_user_agent_string() );

  $data = curl_exec( $curl );
  curl_close( $curl );

  if( $data ) {
    $content_length = "unknown";
    $status = "unknown";

    if( preg_match( "/^HTTP\/1\.[01] (\d\d\d)/", $data, $matches ) ) {
      $status = (int)$matches[1];
    }

    if( preg_match( "/Content-Length: (\d+)/", $data, $matches ) ) {
      $content_length = (int)$matches[1];
    }

    // http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
    if( $status == 200 || ($status > 300 && $status <= 308) ) {
      $result = $content_length;
    }
  }

  return $result;
}
?>

Використання:

$file_size = curl_get_file_size( "http://stackoverflow.com/questions/2602612/php-remote-file-size-without-downloading-file" );

4
Але майте на увазі, що відповіді можуть бути і без Content-length.
VolkerK

4
Чи не краще було б використовувати curl_getinfo, як пропонує @macki?
Свіш

1
@Svish, так, бо такий підхід насправді працює. Представлений тут підхід зазнає невдачі щодо перенаправлених URL-адрес, оскільки він захоплює першу довжину вмісту, яка не є (обов’язково?) Кінцевою довжиною вмісту. З мого досвіду.
Боббі Джек

12
Це не спрацювало для мене, як get_user_agent_string()не було визначено. Видалення цілого рядка змусило все це працювати.
Рапті

1
це терпить невдачу при тестуванні: http://www.dailymotion.com/rss/user/dialhainaut/см SO: stackoverflow.com/questions/36761377 / ...
ErickBest

63

Спробуйте цей код

function retrieve_remote_file_size($url){
     $ch = curl_init($url);

     curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
     curl_setopt($ch, CURLOPT_HEADER, TRUE);
     curl_setopt($ch, CURLOPT_NOBODY, TRUE);

     $data = curl_exec($ch);
     $size = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);

     curl_close($ch);
     return $size;
}

Якщо це не працює для вас, ви можете додати curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);.
mermshaus

3
Мені не підходить для зображення. Я справді CURLOPT_FOLLOWLOCATIONвстановив правду.
Нейт

5
@Abenil додати цей параметр. curl_setopt ($ curl, CURLOPT_SSL_VERIFYPEER, false);
Давіндер Кумар,

1
@Davinder Kumar: велике спасибі, додавши свій код, згаданий код працює.
Trung Le Nguyen Nhat

1
Прошу! @TrungLeNguyenNhat
Davinder Kumar

31

Як уже згадувалося кілька разів, шлях, щоб отримати інформацію з заголовка відповіді в Content-Lengthполе .

Однак слід зазначити це

  • сервер, який ви зондуєте, не обов'язково реалізує метод HEAD (!)
  • немає абсолютно необхідності вручну створювати запит HEAD (який, знову ж таки, можливо, навіть не підтримується), використовуючи fopenабо однаково, або навіть викликати бібліотеку curl, коли PHP має get_headers()(пам'ятайте: KISS )

Використання get_headers()дотримується принципу KISS і працює, навіть якщо сервер, який ви перевіряєте, не підтримує запит HEAD.

Отже, ось моя версія (gimmick: повертає зручний для форматування розмір ;-)):

Суть: https://gist.github.com/eyecatchup/f26300ffd7e50a92bc4d (версія curl та get_headers)
get_headers () - Версія:

<?php     
/**
 *  Get the file size of any remote resource (using get_headers()), 
 *  either in bytes or - default - as human-readable formatted string.
 *
 *  @author  Stephan Schmitz <eyecatchup@gmail.com>
 *  @license MIT <http://eyecatchup.mit-license.org/>
 *  @url     <https://gist.github.com/eyecatchup/f26300ffd7e50a92bc4d>
 *
 *  @param   string   $url          Takes the remote object's URL.
 *  @param   boolean  $formatSize   Whether to return size in bytes or formatted.
 *  @param   boolean  $useHead      Whether to use HEAD requests. If false, uses GET.
 *  @return  string                 Returns human-readable formatted size
 *                                  or size in bytes (default: formatted).
 */
function getRemoteFilesize($url, $formatSize = true, $useHead = true)
{
    if (false !== $useHead) {
        stream_context_set_default(array('http' => array('method' => 'HEAD')));
    }
    $head = array_change_key_case(get_headers($url, 1));
    // content-length of download (in bytes), read from Content-Length: field
    $clen = isset($head['content-length']) ? $head['content-length'] : 0;

    // cannot retrieve file size, return "-1"
    if (!$clen) {
        return -1;
    }

    if (!$formatSize) {
        return $clen; // return size in bytes
    }

    $size = $clen;
    switch ($clen) {
        case $clen < 1024:
            $size = $clen .' B'; break;
        case $clen < 1048576:
            $size = round($clen / 1024, 2) .' KiB'; break;
        case $clen < 1073741824:
            $size = round($clen / 1048576, 2) . ' MiB'; break;
        case $clen < 1099511627776:
            $size = round($clen / 1073741824, 2) . ' GiB'; break;
    }

    return $size; // return formatted size
}

Використання:

$url = 'http://download.tuxfamily.org/notepadplus/6.6.9/npp.6.6.9.Installer.exe';
echo getRemoteFilesize($url); // echoes "7.51 MiB"

Додаткова примітка: Заголовок Content-Length не є обов’язковим. Таким чином, як загальне рішення воно не є кульковим !



2
Це має бути прийнятою відповіддю. Правда, Content-Lengthце необов’язково, але це єдиний спосіб отримати розмір файлу, не завантажуючи його - і get_headersце найкращий спосіб отримати content-length.
Квентін Скузен

2
Майте на увазі, що це змінить налаштування методу запиту на HEAD у всіх наступних запитах HTTP для цього процесу PHP. Використовуйте stream_context_createдля створення окремого контексту для виклику get_headers(7.1+).
MatsLindh

просто додавши, що якщо у вашому URL-адресі або назві файлу DOCUMENT є пробіли, це поверне -1
jasonflaherty

15

Звичайно. Зробіть запит лише на заголовки та знайдіть Content-Lengthзаголовок.


14

Функція Php get_headers()працює для мене, щоб перевірити довжину вмісту як

$headers = get_headers('http://example.com/image.jpg', 1);
$filesize = $headers['Content-Length'];

Детальніше: PHP-функція get_headers ()


4
Для мене (з nginx) заголовком було Content-Length
Pangamma

7

Я не впевнений, але чи не могли б ви використовувати для цього функцію get_headers?

$url     = 'http://example.com/dir/file.txt';
$headers = get_headers($url, true);

if ( isset($headers['Content-Length']) ) {
   $size = 'file size:' . $headers['Content-Length'];
}
else {
   $size = 'file size: unknown';
}

echo $size;

У цьому прикладі цільовий сервер за адресою $ url може використовувати get_headers, щоб підтримувати з'єднання відкритим, доки процес PHP не закінчиться (повертаючи заголовки дуже повільно, хоча і недостатньо повільно, щоб з'єднання заглохло). Оскільки загальні процеси PHP можуть бути обмежені FPM, це може дозволити тип повільної атаки loris, коли кілька "користувачів" одночасно отримують доступ до вашого сценарію get_headers.
Тед Філліпс,

6

найкраще рішення в один рядок:

echo array_change_key_case(get_headers("http://.../file.txt",1))['content-length'];

php занадто delicius

function urlsize($url):int{
   return array_change_key_case(get_headers($url,1))['content-length'];
}

echo urlsize("http://.../file.txt");

3

Найпростіша та найефективніша реалізація:

function remote_filesize($url, $fallback_to_download = false)
{
    static $regex = '/^Content-Length: *+\K\d++$/im';
    if (!$fp = @fopen($url, 'rb')) {
        return false;
    }
    if (isset($http_response_header) && preg_match($regex, implode("\n", $http_response_header), $matches)) {
        return (int)$matches[0];
    }
    if (!$fallback_to_download) {
        return false;
    }
    return strlen(stream_get_contents($fp));
}

OP вказано "без завантаження файлу". Цей метод завантажує файл у пам’ять з віддаленого сервера (наприклад: завантаження). Навіть за умови швидкого з'єднання між серверами, це може легко перерватись або зайняти занадто багато часу для великих файлів. Примітка: Ви ніколи не закривали $ fp, що не є глобальним
Мавело

1
Ця функція НЕ завантажує тіло якомога довше; якщо він містить Content-Lengthзаголовок. І явне $fpзакриття НЕ ПОТРІБНО; він автоматично випускається після закінчення терміну дії. php.net/manual/en/language.types.resource.php
mpyw

Ви можете легко підтвердити вищезазначене за допомогоюnc -l localhost 8080
mpyw

Насправді більшість *closeфункцій не потрібні в сучасному PHP. Вони з двох історичних причин: обмеження реалізації та імітація мови С.
mpyw

Заголовки ненадійні, і резервне завантаження суперечить OP. Нарешті, якщо ви відкриваєте файл, просто закрийте його. Сміттєзбірники не є виправданням для ледачих розробників, які економлять один рядок коду.
Mavelo

2

Оскільки це питання вже позначено тегами "php" та "curl", я припускаю, що ви знаєте, як використовувати Curl у PHP.

Якщо встановити, curl_setopt(CURLOPT_NOBODY, TRUE)тоді ви зробите запит HEAD і, можливо, зможете перевірити заголовок відповіді "Content-Length", який буде лише заголовками.


2

Спробуйте вказану нижче функцію, щоб отримати розмір віддаленого файлу

function remote_file_size($url){
    $head = "";
    $url_p = parse_url($url);

    $host = $url_p["host"];
    if(!preg_match("/[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/",$host)){

        $ip=gethostbyname($host);
        if(!preg_match("/[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/",$ip)){

            return -1;
        }
    }
    if(isset($url_p["port"]))
    $port = intval($url_p["port"]);
    else
    $port    =    80;

    if(!$port) $port=80;
    $path = $url_p["path"];

    $fp = fsockopen($host, $port, $errno, $errstr, 20);
    if(!$fp) {
        return false;
        } else {
        fputs($fp, "HEAD "  . $url  . " HTTP/1.1\r\n");
        fputs($fp, "HOST: " . $host . "\r\n");
        fputs($fp, "User-Agent: http://www.example.com/my_application\r\n");
        fputs($fp, "Connection: close\r\n\r\n");
        $headers = "";
        while (!feof($fp)) {
            $headers .= fgets ($fp, 128);
            }
        }
    fclose ($fp);

    $return = -2;
    $arr_headers = explode("\n", $headers);
    foreach($arr_headers as $header) {

        $s1 = "HTTP/1.1";
        $s2 = "Content-Length: ";
        $s3 = "Location: ";

        if(substr(strtolower ($header), 0, strlen($s1)) == strtolower($s1)) $status = substr($header, strlen($s1));
        if(substr(strtolower ($header), 0, strlen($s2)) == strtolower($s2)) $size   = substr($header, strlen($s2));
        if(substr(strtolower ($header), 0, strlen($s3)) == strtolower($s3)) $newurl = substr($header, strlen($s3));  
    }

    if(intval($size) > 0) {
        $return=intval($size);
    } else {
        $return=$status;
    }

    if (intval($status)==302 && strlen($newurl) > 0) {

        $return = remote_file_size($newurl);
    }
    return $return;
}

Це єдиний, який працював у мене на сервері Ubuntu Linux apache. Мені довелося ініціювати $ size і $ status на початку функції, інакше працював як є.
Гевін Сімпсон

2

Ось ще один підхід, який буде працювати з серверами, які не підтримують HEADзапити.

Він використовує cURL, щоб зробити запит на вміст за допомогою заголовка діапазону HTTP із запитом на перший байт файлу.

Якщо сервер підтримує запити на діапазон (більшість медіасерверів підтримують), тоді він отримає відповідь із розміром ресурсу.

Якщо сервер не відповідає діапазоном байтів, він буде шукати заголовок довжини вмісту, щоб визначити довжину.

Якщо розмір знайдено в заголовку діапазону або довжини вмісту, передача переривається. Якщо розмір не знайдено, і функція починає читати тіло відповіді, передача переривається.

Це може бути додатковим підходом, якщо HEADзапит призводить до відповіді на 405метод, що не підтримується.

/**
 * Try to determine the size of a remote file by making an HTTP request for
 * a byte range, or look for the content-length header in the response.
 * The function aborts the transfer as soon as the size is found, or if no
 * length headers are returned, it aborts the transfer.
 *
 * @return int|null null if size could not be determined, or length of content
 */
function getRemoteFileSize($url)
{
    $ch = curl_init($url);

    $headers = array(
        'Range: bytes=0-1',
        'Connection: close',
    );

    $in_headers = true;
    $size       = null;

    curl_setopt($ch, CURLOPT_HEADER, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2450.0 Iron/46.0.2450.0');
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_VERBOSE, 0); // set to 1 to debug
    curl_setopt($ch, CURLOPT_STDERR, fopen('php://output', 'r'));

    curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $line) use (&$in_headers, &$size) {
        $length = strlen($line);

        if (trim($line) == '') {
            $in_headers = false;
        }

        list($header, $content) = explode(':', $line, 2);
        $header = strtolower(trim($header));

        if ($header == 'content-range') {
            // found a content-range header
            list($rng, $s) = explode('/', $content, 2);
            $size = (int)$s;
            return 0; // aborts transfer
        } else if ($header == 'content-length' && 206 != curl_getinfo($curl, CURLINFO_HTTP_CODE)) {
            // found content-length header and this is not a 206 Partial Content response (range response)
            $size = (int)$content;
            return 0;
        } else {
            // continue
            return $length;
        }
    });

    curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($curl, $data) use ($in_headers) {
        if (!$in_headers) {
            // shouldn't be here unless we couldn't determine file size
            // abort transfer
            return 0;
        }

        // write function is also called when reading headers
        return strlen($data);
    });

    $result = curl_exec($ch);
    $info   = curl_getinfo($ch);

    return $size;
}

Використання:

$size = getRemoteFileSize('http://example.com/video.mp4');
if ($size === null) {
    echo "Could not determine file size from headers.";
} else {
    echo "File size is {$size} bytes.";
}

1
Ваша відповідь мені справді допомогла. Завжди повертає відповідь. Навіть якщо Content-Lengthнедоступний.
Іман Хеджазі

Привіт, спасибі, що подивились та коментували. Я дуже рада, що Ви знайшли це корисним!
draw010

1

Більшість відповідей тут використовує або CURL, або ґрунтуються на читанні заголовків. Але в деяких певних ситуаціях ви можете скористатися простішим рішенням. Розгляньте примітку щодо filesize()документів на PHP.net . Там ви знайдете підказку: " Починаючи з PHP 5.0.0, цю функцію можна також використовувати з деякими обгортками URL. Зверніться до Підтримуваних протоколів та обгортки щоб визначити, які обгортки підтримують функціональність сімейства stat () ".

Отже, якщо ваш сервер і синтаксичний аналізатор PHP налаштовані належним чином, ви можете просто використовувати filesize()функцію, додавши її з повною URL-адресою, вказуючи на віддалений файл, розмір якого ви хочете отримати, і дозволити PHP зробити всю магію.


1

Спробуйте це: я використовую його і отримав хороший результат.

    function getRemoteFilesize($url)
{
    $file_headers = @get_headers($url, 1);
    if($size =getSize($file_headers)){
return $size;
    } elseif($file_headers[0] == "HTTP/1.1 302 Found"){
        if (isset($file_headers["Location"])) {
            $url = $file_headers["Location"][0];
            if (strpos($url, "/_as/") !== false) {
                $url = substr($url, 0, strpos($url, "/_as/"));
            }
            $file_headers = @get_headers($url, 1);
            return getSize($file_headers);
        }
    }
    return false;
}

function getSize($file_headers){

    if (!$file_headers || $file_headers[0] == "HTTP/1.1 404 Not Found" || $file_headers[0] == "HTTP/1.0 404 Not Found") {
        return false;
    } elseif ($file_headers[0] == "HTTP/1.0 200 OK" || $file_headers[0] == "HTTP/1.1 200 OK") {

        $clen=(isset($file_headers['Content-Length']))?$file_headers['Content-Length']:false;
        $size = $clen;
        if($clen) {
            switch ($clen) {
                case $clen < 1024:
                    $size = $clen . ' B';
                    break;
                case $clen < 1048576:
                    $size = round($clen / 1024, 2) . ' KiB';
                    break;
                case $clen < 1073741824:
                    $size = round($clen / 1048576, 2) . ' MiB';
                    break;
                case $clen < 1099511627776:
                    $size = round($clen / 1073741824, 2) . ' GiB';
                    break;
            }
        }
        return $size;

    }
    return false;
}

Тепер тестуйте так:

echo getRemoteFilesize('http://mandasoy.com/wp-content/themes/spacious/images/plain.png').PHP_EOL;
echo getRemoteFilesize('http://bookfi.net/dl/201893/e96818').PHP_EOL;
echo getRemoteFilesize('/programming/14679268/downloading-files-as-attachment-filesize-incorrect').PHP_EOL;

Результати:

24,82 КіБ

912 КіБ

101,85 КіБ


1

Щоб покрити запит HTTP / 2, функцію, надану тут https://stackoverflow.com/a/2602624/2380767, потрібно трохи змінити:

<?php
/**
 * Returns the size of a file without downloading it, or -1 if the file
 * size could not be determined.
 *
 * @param $url - The location of the remote file to download. Cannot
 * be null or empty.
 *
 * @return The size of the file referenced by $url, or -1 if the size
 * could not be determined.
 */
function curl_get_file_size( $url ) {
  // Assume failure.
  $result = -1;

  $curl = curl_init( $url );

  // Issue a HEAD request and follow any redirects.
  curl_setopt( $curl, CURLOPT_NOBODY, true );
  curl_setopt( $curl, CURLOPT_HEADER, true );
  curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
  curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, true );
  curl_setopt( $curl, CURLOPT_USERAGENT, get_user_agent_string() );

  $data = curl_exec( $curl );
  curl_close( $curl );

  if( $data ) {
    $content_length = "unknown";
    $status = "unknown";

    if( preg_match( "/^HTTP\/1\.[01] (\d\d\d)/", $data, $matches ) ) {
      $status = (int)$matches[1];
    } elseif( preg_match( "/^HTTP\/2 (\d\d\d)/", $data, $matches ) ) {
      $status = (int)$matches[1];
    }

    if( preg_match( "/Content-Length: (\d+)/", $data, $matches ) ) {
      $content_length = (int)$matches[1];
    } elseif( preg_match( "/content-length: (\d+)/", $data, $matches ) ) {
        $content_length = (int)$matches[1];
    }

    // http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
    if( $status == 200 || ($status > 300 && $status <= 308) ) {
      $result = $content_length;
    }
  }

  return $result;
}
?>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.