Zdalny rozmiar pliku bez pobierania pliku

Odpowiedzi:

100

Znalazłem coś na ten temat tutaj :

Oto najlepszy sposób (jaki znalazłem), aby uzyskać rozmiar pliku zdalnego. Zwróć uwagę, że żądania HEAD nie otrzymują rzeczywistej treści żądania, po prostu pobierają nagłówki. Zatem wysłanie żądania HEAD do zasobu o wielkości 100 MB zajmie tyle samo czasu, co żądanie HEAD do zasobu o wielkości 1 KB.

<?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;
}
?>

Stosowanie:

$file_size = curl_get_file_size( "http://stackoverflow.com/questions/2602612/php-remote-file-size-without-downloading-file" );
NebuSoft
źródło
4
Pamiętaj jednak, że mogą istnieć odpowiedzi bez długości treści.
VolkerK
4
Czy nie byłoby lepiej w użyciu curl_getinfo, jak sugeruje @macki?
Svish
1
@Svish, tak, ponieważ to podejście faktycznie działa. Przedstawione tutaj podejście zawodzi w przypadku przekierowanych adresów URL, ponieważ pobiera pierwszą długość treści, która niekoniecznie jest końcową długością treści. Z mojego doświadczenia.
Bobby Jack
12
To nie zadziałało, ponieważ get_user_agent_string()nie zostało zdefiniowane. Usunięcie całej linii sprawiło, że całość działała.
Rapti
1
to kończy się niepowodzeniem podczas testowania z: http://www.dailymotion.com/rss/user/dialhainaut/patrz SO: stackoverflow.com/questions/36761377/…
ErickBest Kwietnia
63

Wypróbuj ten kod

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
źródło
Jeśli to nie zadziała, możesz dodać curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);.
mermshaus
3
Nie działa dla mnie na obraz. Ustawiłem się CURLOPT_FOLLOWLOCATIONna prawdę.
Nate
5
@Abenil dodaj ten parametr. curl_setopt ($ curl, CURLOPT_SSL_VERIFYPEER, false);
Davinder Kumar
1
@Davinder Kumar: wielkie dzięki, dodanie twojego kodu sprawi, że powyższy kod będzie działał.
Trung Le Nguyen Nhat
1
Proszę bardzo! @TrungLeNguyenNhat
Davinder Kumar
31

Jak wspomniano kilka razy, do zrobienia jest, aby pobrać informacje z nagłówka odpowiedzi na Content-Lengthpolu .

Jednak powinieneś to zauważyć

  • serwer, który badasz, niekoniecznie implementuje metodę HEAD (!)
  • nie ma absolutnie potrzeby ręcznego tworzenia żądania HEAD (które, znowu, może nawet nie być obsługiwane) przy użyciu fopenlub podobnych, a nawet wywoływać bibliotekę curl, gdy PHP ma get_headers()(pamiętaj: KISS )

Użycie jest get_headers()zgodne z zasadą KISS i działa nawet jeśli sondowany serwer nie obsługuje żądania HEAD.

Oto moja wersja (sztuczka: zwraca sformatowany rozmiar czytelny dla człowieka ;-)):

Streszczenie: https://gist.github.com/eyecatchup/f26300ffd7e50a92bc4d (wersja curl i get_headers)
get_headers () - Wersja:

<?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
}

Stosowanie:

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

Dodatkowa uwaga: nagłówek Content-Length jest opcjonalny. Dlatego jako rozwiązanie ogólne nie jest to kuloodporne !


eyecatchUp
źródło
2
To powinna być akceptowana odpowiedź. To prawda, Content-Lengthjest opcjonalne, ale jest to jedyny sposób na uzyskanie rozmiaru pliku bez jego pobierania - i get_headersjest to najlepszy sposób na uzyskanie content-length.
Quentin Skousen
2
Należy pamiętać, że spowoduje to zmianę preferencji metody żądania na HEAD we wszystkich kolejnych żądaniach HTTP dla tego procesu PHP. Służy stream_context_createdo tworzenia oddzielnego kontekstu do użycia dla wywołania get_headers(7.1+).
MatsLindh
dodając tylko, że jeśli adres URL lub nazwa pliku DOKUMENTU zawiera spacje, zwróci to -1
jasonflaherty
15

Pewnie. Stwórz żądanie tylko nagłówków i poszukaj Content-Lengthnagłówka.

ceejayoz
źródło
14

Funkcja php get_headers()działa dla mnie, aby sprawdzić długość zawartości jako

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

Więcej szczegółów: Funkcja PHP get_headers ()

Sanchit Gupta
źródło
4
Dla mnie (z nginx) nagłówek to Content-Length
Pangamma
7

Nie jestem pewien, ale czy nie mógłbyś użyć do tego funkcji 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;
Jake
źródło
W tym przykładzie możliwe jest, że serwer docelowy pod adresem $ url wykorzysta get_headers do utrzymywania połączenia otwartego, dopóki proces PHP nie przekroczy limitu czasu (przez bardzo powolne zwracanie nagłówków, ale nie na tyle wolno, aby połączenie stało się nieaktualne). Ponieważ wszystkie procesy PHP mogą być ograniczone przez FPM, może to pozwolić na powolny atak loris, gdy wielu „użytkowników” uzyskuje dostęp do skryptu get_headers jednocześnie.
Ted Phillips,
6

najlepsze rozwiązanie w jednej linii:

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

php jest zbyt delikatny

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

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

źródło
3

Najprostsza i najbardziej wydajna realizacja:

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
źródło
OP wskazał „bez pobierania pliku”. Ta metoda ładuje plik do pamięci ze zdalnego serwera (np. Pobieranie). Nawet przy szybkich połączeniach między serwerami może to łatwo przekroczyć limit czasu lub zająć zbyt dużo czasu w przypadku dużych plików. Uwaga: nigdy nie zamknąłeś $ fp, który nie jest objęty zakresem globalnym
Mavelo
1
Ta funkcja NIE pobiera treści tak długo, jak to możliwe; jeśli zawiera Content-Lengthnagłówek. A wyraźne $fpzamknięcie NIE JEST KONIECZNE; jest automatycznie zwalniany po wygaśnięciu. php.net/manual/en/language.types.resource.php
mpyw
Możesz to łatwo potwierdzić za pomocąnc -l localhost 8080
mpyw
W rzeczywistości większość *closefunkcji nie jest potrzebna we współczesnym PHP. Wynikają one z dwóch historycznych powodów: ograniczenia implementacji i naśladowania języka C.
mpyw,
Nagłówki są zawodne, a pobieranie awaryjne jest sprzeczne z OP. Wreszcie, jeśli otworzysz plik, po prostu go zamknij. Kolektory bezużyteczne nie są wymówką dla leniwych programistów zapisujących pojedynczą linię kodu.
Mavelo
2

Ponieważ to pytanie jest już oznaczone jako „php” i „curl”, zakładam, że wiesz, jak używać Curl w PHP.

Jeśli ustawisz, curl_setopt(CURLOPT_NOBODY, TRUE)wykonasz żądanie HEAD i prawdopodobnie będziesz mógł sprawdzić nagłówek „Content-Length” odpowiedzi, który będzie tylko nagłówkami.

dkaminy
źródło
2

Wypróbuj poniższą funkcję, aby uzyskać rozmiar pliku zdalnego

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;
}
Rahul Kaushik
źródło
Jest to jedyny, który działał dla mnie na serwerze apache Ubuntu Linux. Musiałem zainicjować $ size i $ status na początku funkcji, w przeciwnym razie działałem tak, jak jest.
Gavin Simpson
2

Oto inne podejście, które będzie działać z serwerami, które nie obsługują HEADżądań.

Używa cURL do żądania treści z nagłówkiem zakresu HTTP z pytaniem o pierwszy bajt pliku.

Jeśli serwer obsługuje żądania dotyczące zakresu (większość serwerów multimediów), otrzyma odpowiedź z informacją o rozmiarze zasobu.

Jeśli serwer nie odpowie podając zakres bajtów, będzie szukał nagłówka content-length, aby określić długość.

Jeśli rozmiar zostanie znaleziony w nagłówku zakresu lub długości zawartości, przesyłanie jest przerywane. Jeśli rozmiar nie zostanie znaleziony, a funkcja zacznie odczytywać treść odpowiedzi, przesyłanie zostanie przerwane.

Może to być podejście uzupełniające, jeśli HEADżądanie skutkuje 405odpowiedzią na nieobsługiwaną metodę.

/**
 * 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;
}

Stosowanie:

$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
źródło
1
Twoja odpowiedź naprawdę mi pomogła. Zawsze zwraca odpowiedź. Nawet jeśli Content-Lengthnie jest dostępny.
Iman Hejazi
Cześć, dzięki za opiekę i komentarz. Bardzo się cieszę, że okazało się to pomocne!
drew010
1

Większość odpowiedzi używa CURL lub opiera się na czytaniu nagłówków. Ale w niektórych sytuacjach możesz użyć o wiele prostszego rozwiązania. Zwróć uwagę na filesize()dokumentację na PHP.net . Znajdziesz tam wskazówkę mówiącą: " Od PHP 5.0.0, ta funkcja może być również używana z niektórymi opakowaniami URL. Zobacz Obsługiwane protokoły i opakowania, aby określić, które opakowania obsługują rodzinę funkcji stat () ".

Tak więc, jeśli twój serwer i parser PHP są odpowiednio skonfigurowane, możesz po prostu użyć filesize()funkcji, podać mu pełny adres URL, wskazując zdalny plik, którego rozmiar chcesz uzyskać, i pozwolić PHP zrobić całą magię.

trejder
źródło
1

Spróbuj tego: używam go i uzyskałem dobry wynik.

    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;
}

Teraz przetestuj w ten sposób:

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;

Wyniki:

24,82 KiB

912 KiB

101,85 KiB

josef
źródło
1

Aby pokryć żądanie HTTP / 2, funkcja podana tutaj https://stackoverflow.com/a/2602624/2380767 musi zostać nieco zmieniona:

<?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
źródło