Размер удаленного файла без загрузки файла

Ответы:

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" );
NebuSoft
источник
5
Но имейте в виду, что могут быть ответы без Content-length.
VolkerK
4
Не лучше ли использовать curl_getinfo, как предлагает @macki?
Svish
1
@Svish, да, потому что такой подход действительно работает. Представленный здесь подход не работает с перенаправленными URL-адресами, поскольку он захватывает первую Content-Length, которая не является (обязательно?) Конечной Content-Length. По моему опыту.
Бобби Джек,
12
Это не сработало для меня, поскольку get_user_agent_string()не было определено. Удаление всей линии заставило все работать.
Rapti
2
если сервер не поддерживает HEAD, он вернет 405
xiaoyifang
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;
}
Macki
источник
Если это не сработает, вы можете добавить curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);.
mermshaus 01
3
Не работает для изображения. Я CURLOPT_FOLLOWLOCATIONустановил истинное значение.
Нейт
5
@Abenil добавьте этот параметр. curl_setopt ($ curl, CURLOPT_SSL_VERIFYPEER, ложь);
Davinder Kumar
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.

Итак, вот моя версия (трюк: возвращает размер в удобочитаемом формате ;-)):

Суть: 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 необязателен. Таким образом, в качестве общего решения это не пуленепробиваемое !


EyecatchUp
источник
2
Это должен быть принятый ответ. Правда, Content-Lengthэто необязательно, но это единственный способ получить размер файла, не загружая его, и get_headersэто лучший способ получить content-length.
Quentin
2
Имейте в виду, что это изменит предпочтение метода запроса на HEAD во всех последующих HTTP-запросах для этого процесса PHP. Используйте stream_context_createдля создания отдельного контекста, который будет использоваться для вызова get_headers(7.1+).
MatsLindh 06
просто добавив, что если в вашем URL-адресе или имени файла DOCUMENT есть пробелы, это вернет -1
jasonflaherty
15

Конечно. Сделайте запрос только с заголовками и найдите Content-Lengthзаголовок.

Ceejayoz
источник
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 слишком деликатен

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));
}
mpyw
источник
ОП указал "без скачивания файла". Этот метод загружает файл в память с удаленного сервера (например: загрузка). Даже при быстром соединении между серверами это может легко истечь или занять слишком много времени для больших файлов. Примечание: вы никогда не закрывали $ fp, который не входит в глобальную область видимости
Mavelo
1
Эта функция НЕ загружает тело как можно дольше; если он содержит Content-Lengthзаголовок. И явное $fpзакрытие НЕ НУЖНО; он автоматически выпускается по истечении срока действия. php.net/manual/en/language.types.resource.php
mpyw
Вы можете легко подтвердить сказанное выше, используяnc -l localhost 8080
mpyw
На самом деле большинство *closeфункций в современном PHP не нужны. Они происходят по двум историческим причинам: ограничения реализации и имитация языка C.
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;
}
Рахул Кошик
источник
Это единственный, который работал у меня на сервере Apache Ubuntu Linux. Мне нужно было инициализировать $ 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.";
}
drew010
источник
1
Ваш ответ мне очень помог. Всегда возвращает ответ. Даже если Content-Lengthнет в наличии.
Иман
Привет, спасибо, что посмотрели и комментировали. Я действительно рада, что вы нашли это полезным!
drew010
1

Большинство ответов здесь используют либо CURL, либо основаны на чтении заголовков. Но в некоторых ситуациях вы можете использовать более простое решение. Обратите внимание на filesize()документацию на PHP.net . Вы найдете там подсказку: « Начиная с PHP 5.0.0, эту функцию также можно использовать с некоторыми оболочками URL. Обратитесь к разделу Поддерживаемые протоколы и оболочки, чтобы определить, какие оболочки поддерживают семейство функций stat () ».

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

Trejder
источник
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;
}
?>
IonV
источник