Dimensione del file remoto senza scaricare il file


Risposte:


100

Ho trovato qualcosa al riguardo qui :

Ecco il modo migliore (che ho trovato) per ottenere la dimensione di un file remoto. Tieni presente che le richieste HEAD non ottengono il corpo effettivo della richiesta, ma recuperano solo le intestazioni. Quindi, fare una richiesta HEAD a una risorsa di 100 MB richiederà la stessa quantità di tempo di una richiesta HEAD a una risorsa di 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;
}
?>

Utilizzo:

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

5
Ma tieni presente che possono esserci risposte senza lunghezza del contenuto.
VolkerK

4
Non sarebbe meglio usare curl_getinfo, come suggerisce @macki?
Svish

1
@ Svish, sì, perché quell'approccio funziona davvero. L'approccio qui presentato fallisce sugli URL reindirizzati, poiché acquisisce il primo Content-Length che non è (necessariamente?) Il Content-Length finale . Nella mia esperienza.
Bobby Jack

12
Questo non ha funzionato per me in quanto get_user_agent_string()non è stato definito. La rimozione dell'intera linea ha fatto funzionare il tutto.
Rapti

2
se il server non supporta HEAD, restituirà 405
xiaoyifang

63

Prova questo codice

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

Se questo non funziona per te, potresti voler aggiungere curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);.
mermshaus

3
Non funziona per me per un'immagine. Ho CURLOPT_FOLLOWLOCATIONimpostato su true.
Nate

5
@Abenil aggiungi questo parametro. curl_setopt ($ curl, CURLOPT_SSL_VERIFYPEER, false);
Davinder Kumar

1
@Davinder Kumar: grazie mille, l'aggiunta del codice fa funzionare il codice sopra.
Trung Le Nguyen Nhat

1
Prego! @TrungLeNguyenNhat
Davinder Kumar

31

Come accennato un paio di volte, la strada da percorrere è recuperare le informazioni dal Content-Lengthcampo dell'intestazione della risposta .

Tuttavia, dovresti tenerlo presente

  • il server che stai sondando non implementa necessariamente il metodo HEAD (!)
  • non è assolutamente necessario creare manualmente una richiesta HEAD (che, ancora una volta, potrebbe non essere supportata) utilizzando fopeno simili o persino per invocare la libreria curl, quando PHP ha get_headers()(ricorda: KISS )

L'uso di get_headers()segue il principio KISS e funziona anche se il server che stai sondando non supporta la richiesta HEAD.

Quindi, ecco la mia versione (espediente: restituisce dimensioni formattate leggibili dall'uomo ;-)):

Gist: https://gist.github.com/eyecatchup/f26300ffd7e50a92bc4d (versione curl e get_headers)
get_headers () - Versione:

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

Utilizzo:

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

Nota aggiuntiva: l'intestazione Content-Length è facoltativa. Quindi, come soluzione generale, non è a prova di proiettile !



2
Questa dovrebbe essere la risposta accettata. È vero, Content-Lengthè facoltativo, ma è l'unico modo per ottenere la dimensione del file senza scaricarlo ed get_headersè il modo migliore per ottenerlo content-length.
Quentin Skousen

2
Tieni presente che questo cambierà la preferenza per il metodo di richiesta in HEAD all'interno di tutte le successive richieste HTTP per questo processo PHP. Utilizzare stream_context_createper creare un contesto separato da utilizzare per la chiamata a get_headers(7.1+).
MatsLindh

aggiungendo semplicemente che se l'URL o il nome del file DOCUMENTO contiene spazi, verrà restituito un -1
jasonflaherty

15

Sicuro. Fai una richiesta di sole intestazioni e cerca l' Content-Lengthintestazione.


14

La funzione php funziona get_headers()per me per controllare la lunghezza del contenuto come

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

Per maggiori dettagli: Funzione PHP get_headers ()


4
Per me (con nginx) l'intestazione era Content-Length
Pangamma

7

Non ne sono sicuro, ma non potresti usare la funzione get_headers per questo?

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

Con questo esempio, è possibile per il server di destinazione a $ url sfruttare get_headers per mantenere la connessione aperta fino al timeout del processo PHP (restituendo le intestazioni molto lentamente, ma non abbastanza lentamente da lasciare che la connessione diventi obsoleta). Poiché i processi PHP totali potrebbero essere limitati da FPM, questo può consentire un tipo di attacco loris lento quando più "utenti" accedono allo script get_headers contemporaneamente.
Ted Phillips

6

una linea migliore soluzione:

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

php è troppo delizioso

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

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

3

L'implementazione più semplice ed efficiente:

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 ha indicato "senza scaricare il file". Questo metodo carica il file in memoria dal server remoto (ad esempio: download). Anche con connessioni veloci tra server, questo può facilmente scadere o richiedere troppo tempo su file di grandi dimensioni. Nota: non hai mai chiuso $ fp che non è nell'ambito globale
Mavelo

1
Questa funzione NON scarica il corpo il più a lungo possibile; se contiene Content-Lengthintestazione. E la $fpchiusura esplicita NON è NECESSARIA; viene rilasciato automaticamente alla scadenza. php.net/manual/en/language.types.resource.php
mpyw

Puoi facilmente confermare quanto sopra usandonc -l localhost 8080
mpyw

In realtà la maggior parte delle *closefunzioni non sono necessarie nel moderno PHP. Derivano da due ragioni storiche: restrizione dell'implementazione e imitazione del linguaggio C.
mpyw

Le intestazioni non sono affidabili e il download di fallback va contro l'OP. Infine, se apri un file, chiudilo semplicemente. I garbage collector non sono una scusa per sviluppatori pigri che salvano una singola riga di codice.
Mavelo

2

Dato che questa domanda è già contrassegnata come "php" e "curl", presumo che tu sappia come usare Curl in PHP.

Se imposti curl_setopt(CURLOPT_NOBODY, TRUE), farai una richiesta HEAD e probabilmente puoi controllare l'intestazione "Content-Length" della risposta, che sarà solo intestazioni.


2

Prova la funzione seguente per ottenere la dimensione del file remoto

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

Questo è l'unico che ha funzionato per me sul server Apache di Ubuntu Linux. Ho dovuto inizializzare $ size e $ status all'inizio della funzione, altrimenti funzionava così com'è.
Gavin Simpson

2

Ecco un altro approccio che funzionerà con i server che non supportano le HEADrichieste.

Utilizza cURL per effettuare una richiesta per il contenuto con un'intestazione di intervallo HTTP che richiede il primo byte del file.

Se il server supporta le richieste di intervallo (la maggior parte dei server multimediali lo farà), riceverà la risposta con la dimensione della risorsa.

Se il server non risponde con un intervallo di byte, cercherà un'intestazione della lunghezza del contenuto per determinare la lunghezza.

Se la dimensione si trova in un intervallo o in un'intestazione della lunghezza del contenuto, il trasferimento viene interrotto. Se la dimensione non viene trovata e la funzione inizia a leggere il corpo della risposta, il trasferimento viene interrotto.

Questo potrebbe essere un approccio supplementare se una HEADrichiesta risulta in una 405risposta non supportata dal metodo.

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

Utilizzo:

$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
La tua risposta mi ha davvero aiutato. Restituisce sempre la risposta. Anche se Content-Lengthnon è disponibile.
Iman Hejazi

Ciao, grazie per aver guardato e commentato. Sono davvero contento che tu l'abbia trovato utile!
pareggiò 010

1

La maggior parte delle risposte qui utilizza CURL o si basa sulla lettura delle intestazioni. Ma in alcune situazioni puoi usare una soluzione molto più semplice. Considera le note sui filesize()documenti di PHP.net . Troverai un suggerimento che dice: " A partire da PHP 5.0.0, questa funzione può essere utilizzata anche con alcuni wrapper URL. Fare riferimento a Protocolli e wrapper supportati per determinare quali wrapper supportano la famiglia di funzionalità stat () ".

Quindi, se il tuo server e il parser PHP sono configurati correttamente, puoi semplicemente usare la filesize()funzione, alimentarla con l'URL completo, puntare a un file remoto, la dimensione che vuoi ottenere, e lasciare che PHP faccia la magia.


1

Prova questo: lo uso e ho ottenuto un buon risultato.

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

Ora, prova in questo modo:

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;

Risultati:

24.82 KiB

912 KiB

101,85 KiB


1

Per coprire la richiesta HTTP / 2, la funzione fornita qui https://stackoverflow.com/a/2602624/2380767 deve essere leggermente modificata:

<?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;
}
?>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.