Tamaño de archivo remoto sin descargar archivo

Respuestas:

100

Encontré algo sobre esto aquí :

Esta es la mejor manera (que he encontrado) de obtener el tamaño de un archivo remoto. Tenga en cuenta que las solicitudes HEAD no obtienen el cuerpo real de la solicitud, solo recuperan los encabezados. Por lo tanto, realizar una solicitud HEAD a un recurso de 100 MB llevará la misma cantidad de tiempo que una solicitud HEAD a un recurso de 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;
}
?>

Uso:

$file_size = curl_get_file_size( "http://stackoverflow.com/questions/2602612/php-remote-file-size-without-downloading-file" );
NebuSoft
fuente
4
Pero tenga en cuenta que puede haber respuestas sin longitud de contenido.
VolkerK
4
¿No sería mejor usarlo curl_getinfo, como sugiere @macki?
Svish
1
@Svish, sí, porque ese enfoque realmente funciona. El enfoque que se presenta aquí falla en las URL redirigidas, ya que toma la primera longitud del contenido que no es (¿necesariamente?) La longitud del contenido final . En mi experiencia.
Bobby Jack
12
Esto no funcionó para mí ya que get_user_agent_string()no estaba definido. Eliminar toda la línea hizo que todo funcionara.
Rapti
1
esto falla cuando se prueba con: http://www.dailymotion.com/rss/user/dialhainaut/ver SO: stackoverflow.com/questions/36761377/…
ErickBest
63

Prueba este código

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
fuente
Si esto no funciona para usted, es posible que desee agregar curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);.
mermshaus
3
No me funciona para una imagen. Me he CURLOPT_FOLLOWLOCATIONfijado en verdad.
Nate
5
@Abenil agrega este parámetro. curl_setopt ($ curl, CURLOPT_SSL_VERIFYPEER, falso);
Davinder Kumar
1
@Davinder Kumar: muchas gracias, agregar su código hace que el código anterior funcione.
Trung Le Nguyen Nhat
1
¡De nada! @TrungLeNguyenNhat
Davinder Kumar
31

Como se mencionó un par de veces, el camino a seguir es recuperar la información del Content-Lengthcampo del encabezado de respuesta .

Sin embargo, debe tener en cuenta que

  • el servidor que está probando no necesariamente implementa el método HEAD (!)
  • no hay absolutamente ninguna necesidad de crear manualmente una solicitud HEAD (que, nuevamente, puede que ni siquiera sea compatible) usando fopeno similar o incluso para invocar la biblioteca curl, cuando PHP lo ha hecho get_headers()(recuerde: KISS )

El uso de get_headers()sigue el principio KISS y funciona incluso si el servidor que está probando no admite la solicitud HEAD.

Entonces, aquí está mi versión (truco: devuelve un tamaño formateado legible por humanos ;-)):

Gist: https://gist.github.com/eyecatchup/f26300ffd7e50a92bc4d (versión curl y get_headers)
get_headers () - Versión:

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

Uso:

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

Nota adicional: el encabezado Content-Length es opcional. Por lo tanto, como solución general, ¡ no es a prueba de balas !


eyecatchUp
fuente
2
Esta debería ser la respuesta aceptada. Es cierto que Content-Lengthes opcional, pero es la única manera de obtener el tamaño del archivo sin descargarlo, y get_headerses la mejor manera de obtenerlo content-length.
Quentin Skousen
2
Tenga en cuenta que esto cambiará la preferencia del método de solicitud a HEAD dentro de todas las solicitudes HTTP posteriores para este proceso PHP. Úselo stream_context_createpara crear un contexto separado para usar para la llamada a get_headers(7.1+).
MatsLindh
simplemente agregando, que si su URL o nombre de archivo DOCUMENTO tiene espacios, esto devolverá un -1
jasonflaherty
15

Por supuesto. Realice una solicitud de solo encabezados y busque el Content-Lengthencabezado.

ceejayoz
fuente
14

La función PHP get_headers()me funciona para verificar la longitud del contenido como

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

Para más detalles: Función PHP get_headers ()

Sanchit Gupta
fuente
4
Para mí (con nginx) el encabezado era Content-Length
Pangamma
7

No estoy seguro, pero ¿no podrías usar la función get_headers para esto?

$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
fuente
Con este ejemplo, es posible que el servidor de destino en $ url explote get_headers para mantener la conexión abierta hasta que el proceso de PHP se agote (devolviendo los encabezados muy lentamente, aunque no lo suficientemente lento como para dejar que la conexión se vuelva obsoleta). Dado que los procesos PHP totales pueden estar limitados por FPM, esto puede permitir un tipo de ataque de loris lento cuando varios "usuarios" acceden a su script get_headers simultáneamente.
Ted Phillips
6

la mejor solución de una línea:

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

php es demasiado delicioso

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

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

fuente
3

La implementación más simple y eficiente:

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
fuente
OP indicó "sin descargar el archivo". Este método carga el archivo en la memoria desde el servidor remoto (por ejemplo: descarga). Incluso con conexiones rápidas entre servidores, esto puede agotar el tiempo de espera o demorar demasiado en archivos grandes. Nota: Nunca cerró $ fp que no está en el alcance global
Mavelo
1
Esta función NO descarga el cuerpo el mayor tiempo posible; si contiene Content-Lengthencabezado. Y el $fpcierre explícito NO ES NECESARIO; se libera automáticamente al expirar. php.net/manual/en/language.types.resource.php
mpyw
Puede confirmar fácilmente lo anterior usandonc -l localhost 8080
mpyw
En realidad, la mayoría de las *closefunciones no son necesarias en PHP moderno. Son por dos razones históricas: restricción de implementación e imitación del lenguaje C.
mpyw
Los encabezados no son confiables y la descarga de respaldo va en contra de OP. Finalmente, si abre un archivo, simplemente ciérrelo. Los recolectores de basura no son una excusa para que los desarrolladores perezosos guarden una sola línea de código.
Mavelo
2

Dado que esta pregunta ya está etiquetada como "php" y "curl", supongo que sabe cómo usar Curl en PHP.

Si lo configura curl_setopt(CURLOPT_NOBODY, TRUE), hará una solicitud HEAD y probablemente pueda verificar el encabezado "Content-Length" de la respuesta, que serán solo encabezados.

dkamins
fuente
2

Pruebe la siguiente función para obtener el tamaño del archivo 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;
}
Rahul Kaushik
fuente
Este es el único que funcionó para mí en el servidor Apache de Ubuntu Linux. Tuve que init $ size y $ status al comienzo de la función, de lo contrario funcionó como está.
Gavin Simpson
2

Aquí hay otro enfoque que funcionará con servidores que no admiten HEADsolicitudes.

Utiliza cURL para realizar una solicitud de contenido con un encabezado de rango HTTP que solicita el primer byte del archivo.

Si el servidor admite solicitudes de rango (la mayoría de los servidores de medios lo harán), recibirá la respuesta con el tamaño del recurso.

Si el servidor no responde con un rango de bytes, buscará un encabezado de longitud de contenido para determinar la longitud.

Si el tamaño se encuentra en un encabezado de rango o longitud de contenido, la transferencia se cancela. Si no se encuentra el tamaño y la función comienza a leer el cuerpo de la respuesta, la transferencia se cancela.

Este podría ser un enfoque complementario si una HEADsolicitud da como resultado un 405método no admitido como respuesta.

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

Uso:

$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
fuente
1
Tu respuesta realmente me ayudó. Siempre devuelve la respuesta. Incluso si Content-Lengthno está disponible.
Iman Hejazi
Hola, gracias por mirar y comentar. ¡Estoy muy contento de que te haya resultado útil!
drew010
1

La mayoría de las respuestas aquí usan CURL o se basan en la lectura de encabezados. Pero en algunas situaciones determinadas, puede utilizar una solución mucho más sencilla. Considere la nota sobre filesize()los documentos de PHP.net . Allí encontrará un consejo que dice: " A partir de PHP 5.0.0, esta función también se puede utilizar con algunos envoltorios de URL. Consulte Protocolos y envoltorios admitidos para determinar qué envoltorios admiten la familia de funciones stat () ".

Por lo tanto, si su servidor y analizador de PHP están configurados correctamente, simplemente puede usar la filesize()función, alimentarla con la URL completa, señalar un archivo remoto, el tamaño que desea obtener, y dejar que PHP haga toda la magia.

trejder
fuente
1

Prueba esto: lo uso y obtuve un buen resultado.

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

Ahora, prueba así:

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;

Resultados:

24,82 KiB

912 KiB

101,85 KiB

josef
fuente
1

Para cubrir la solicitud HTTP / 2, la función proporcionada aquí https://stackoverflow.com/a/2602624/2380767 debe cambiarse un poco:

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