Taille du fichier distant sans téléchargement de fichier


Réponses:


100

J'ai trouvé quelque chose à ce sujet ici :

Voici le meilleur moyen (que j'ai trouvé) d'obtenir la taille d'un fichier distant. Notez que les requêtes HEAD n'obtiennent pas le corps réel de la requête, elles récupèrent simplement les en-têtes. Ainsi, faire une demande HEAD à une ressource de 100 Mo prendra le même temps qu'une demande HEAD à une ressource de 1 Ko.

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

Usage:

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

4
Mais gardez à l'esprit qu'il peut y avoir des réponses sans Content-length.
VolkerK

4
Ne serait-il pas préférable d'utiliser curl_getinfo, comme le suggère @macki?
Svish

1
@Svish, oui, car cette approche fonctionne réellement. L'approche présentée ici échoue sur les URL redirigées, car elle saisit le premier Content-Length qui n'est pas (nécessairement?) Le Content-Length final . Dans mon expérience.
Bobby Jack

12
Cela n'a pas fonctionné pour moi car cela get_user_agent_string()n'a pas été défini. La suppression de toute la ligne a fait fonctionner le tout.
Rapti

1
cela échoue lorsqu'il est testé avec: http://www.dailymotion.com/rss/user/dialhainaut/voir SO: stackoverflow.com/questions/36761377/…
ErickBest

63

Essayez ce code

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

Si cela ne fonctionne pas pour vous, vous voudrez peut-être ajouter curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);.
mermshaus

3
Cela ne fonctionne pas pour moi pour une image. Je me suis CURLOPT_FOLLOWLOCATIONmis à vrai.
Nate

5
@Abenil ajoute ce paramètre. curl_setopt ($ curl, CURLOPT_SSL_VERIFYPEER, false);
Davinder Kumar

1
@Davinder Kumar: merci beaucoup, l'ajout de votre code permet au code ci-dessus de fonctionner.
Trung Le Nguyen Nhat

1
Vous êtes le bienvenu! @TrungLeNguyenNhat
Davinder Kumar

31

Comme mentionné à plusieurs reprises, la solution consiste à récupérer les informations du Content-Lengthchamp de l'en-tête de réponse .

Cependant, vous devez noter que

  • le serveur que vous sondez n'implémente pas nécessairement la méthode HEAD (!)
  • il n'est absolument pas nécessaire de créer manuellement une requête HEAD (qui, encore une fois, peut même ne pas être prise en charge) en utilisant fopenou de la même manière ou même d'appeler la bibliothèque curl, lorsque PHP a get_headers()(rappelez-vous: KISS )

L'utilisation de get_headers()suit le principe KISS et fonctionne même si le serveur que vous testez ne prend pas en charge la requête HEAD.

Alors, voici ma version (gimmick: renvoie une taille formatée lisible par l'homme ;-)):

Gist: https://gist.github.com/eyecatchup/f26300ffd7e50a92bc4d (version curl et get_headers)
get_headers () - Version:

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

Usage:

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

Remarque supplémentaire: l'en -tête Content-Length est facultatif. Ainsi, en tant que solution générale, ce n'est pas pare-balles !



2
Cela devrait être la réponse acceptée. Certes, Content-Lengthc'est facultatif, mais c'est le seul moyen d'obtenir la taille du fichier sans le télécharger - et get_headersc'est le meilleur moyen d'obtenir content-length.
Quentin Skousen

2
Sachez que cela changera la préférence de la méthode de requête à HEAD dans toutes les requêtes HTTP suivantes pour ce processus PHP. Utilisez stream_context_createpour créer un contexte distinct à utiliser pour l'appel à get_headers(7.1+).
MatsLindh

en ajoutant simplement que si votre URL ou votre nom de fichier DOCUMENT contient des espaces, cela renverra un -1
jasonflaherty

15

Sûr. Faites une demande d'en-têtes uniquement et recherchez l'en- Content-Lengthtête.


14

La fonction php fonctionne get_headers()pour moi pour vérifier la longueur du contenu comme

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

Pour plus de détails: Fonction PHP get_headers ()


4
Pour moi (avec nginx), l'en-tête était Content-Length
Pangamma

7

Je ne suis pas sûr, mais ne pouvez-vous pas utiliser la fonction get_headers pour cela?

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

Avec cet exemple, il est possible pour le serveur cible à $ url d'exploiter get_headers pour maintenir la connexion ouverte jusqu'à ce que le processus PHP expire (en renvoyant les en-têtes très lentement, mais pas assez lentement pour que la connexion devienne périmée). Étant donné que le nombre total de processus PHP peut être limité par FPM, cela peut permettre un type d'attaque loris lente lorsque plusieurs "utilisateurs" accèdent simultanément à votre script get_headers.
Ted Phillips

6

une ligne meilleure solution:

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

php est trop délicieux

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

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

3

La mise en œuvre la plus simple et la plus efficace:

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 a indiqué «sans télécharger le fichier». Cette méthode charge le fichier en mémoire depuis le serveur distant (ex: téléchargement). Même avec des connexions rapides entre les serveurs, cela peut facilement expirer ou prendre beaucoup trop de temps sur des fichiers volumineux. Remarque: vous n'avez jamais fermé $ fp qui n'est pas dans la portée globale
Mavelo

1
Cette fonction NE télécharge PAS le corps aussi longtemps que possible; s'il contient un en- Content-Lengthtête. Et la $fpfermeture explicite n'est PAS NÉCESSAIRE; il est automatiquement libéré à l'expiration. php.net/manual/en/language.types.resource.php
mpyw

Vous pouvez facilement confirmer ce qui précède en utilisantnc -l localhost 8080
mpyw

En fait, la plupart des *closefonctions ne sont pas nécessaires dans PHP moderne. Ils proviennent de deux raisons historiques: la restriction de l'implémentation et l'imitation du langage C.
mpyw

Les en-têtes ne sont pas fiables et le téléchargement de secours va à l'encontre de OP. Enfin, si vous ouvrez un fichier, fermez-le simplement. Les garbage collector ne sont pas une excuse pour les développeurs paresseux qui enregistrent une seule ligne de code.
Mavelo

2

Puisque cette question est déjà étiquetée "php" et "curl", je suppose que vous savez comment utiliser Curl en PHP.

Si vous définissez, curl_setopt(CURLOPT_NOBODY, TRUE)vous ferez une requête HEAD et pourrez probablement vérifier l'en-tête "Content-Length" de la réponse, qui ne sera que des en-têtes.


2

Essayez la fonction ci-dessous pour obtenir la taille du fichier distant

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

C'est le seul qui a fonctionné pour moi sur le serveur apache Ubuntu Linux. J'ai dû initier $ size et $ status au début de la fonction, sinon cela fonctionnait tel quel.
Gavin Simpson

2

Voici une autre approche qui fonctionnera avec des serveurs qui ne prennent pas en charge les HEADdemandes.

Il utilise cURL pour faire une requête pour le contenu avec un en-tête de plage HTTP demandant le premier octet du fichier.

Si le serveur prend en charge les demandes de plage (la plupart des serveurs de médias le feront), il recevra la réponse avec la taille de la ressource.

Si le serveur ne répond pas avec une plage d'octets, il recherchera un en-tête de longueur de contenu pour déterminer la longueur.

Si la taille se trouve dans un en-tête de plage ou de longueur de contenu, le transfert est interrompu. Si la taille n'est pas trouvée et que la fonction commence à lire le corps de la réponse, le transfert est interrompu.

Cela pourrait être une approche supplémentaire si une HEADdemande aboutit à une 405réponse de méthode non prise en charge.

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

Usage:

$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
Votre réponse m'a vraiment aidé. Renvoie toujours la réponse. Même si Content-Lengthn'est pas disponible.
Iman Hejazi

Salut, merci d'avoir regardé et commenté. Je suis vraiment content que vous l'ayez trouvé utile!
drew010

1

La plupart des réponses ici utilisent CURL ou sont basées sur la lecture des en-têtes. Mais dans certaines situations, vous pouvez utiliser une solution bien plus simple. filesize()Prenez note de la documentation de PHP.net . Vous y trouverez une astuce disant: " Depuis PHP 5.0.0, cette fonction peut également être utilisée avec certains wrappers d'URL. Reportez-vous à Protocoles et wrappers pris en charge pour déterminer quels wrappers supportent la famille de fonctionnalités stat () ".

Donc, si votre serveur et votre analyseur PHP sont correctement configurés, vous pouvez simplement utiliser la filesize()fonction, l'ajouter avec l'URL complète, pointant vers un fichier distant, quelle taille vous voulez obtenir, et laisser PHP faire le tout.


1

Essayez ceci: je l'utilise et j'ai obtenu de bons résultats.

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

Maintenant, testez comme ceci:

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;

Résultats:

24,82 Ko

912 Ko

101,85 Ko


1

Pour couvrir la requête HTTP / 2, la fonction fournie ici https://stackoverflow.com/a/2602624/2380767 doit être légèrement modifiée:

<?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;
}
?>
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.