无需下载文件的远程文件大小

Posted

技术标签:

【中文标题】无需下载文件的远程文件大小【英文标题】:Remote file size without downloading file 【发布时间】:2011-02-05 20:28:49 【问题描述】:

有没有办法在不下载文件的情况下获取远程文件http://my_url/my_file.txt 的大小?

【问题讨论】:

【参考方案1】:

发现了一些关于这个here:

这是获取遥控器大小的最佳方法(我发现) 文件。请注意,HEAD 请求不会获得请求的实际正文, 他们只是检索标题。所以向资源发出 HEAD 请求 即 100MB 将花费与 HEAD 请求相同的时间 1KB 的资源。

<?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://***.com/questions/2602612/php-remote-file-size-without-downloading-file" );

【讨论】:

但请记住,可以在没有 Content-length 的情况下进行响应。 像@macki 建议的那样使用curl_getinfo 不是更好吗? 这对我不起作用,因为 get_user_agent_string() 没有定义。删除整条线使整个工作正常。 如果服务器不支持 HEAD 则返回 405 与@Rapti 一样,我收到了get_user_agent_string() 的错误消息,这可能是代码中遗漏的本地函数。它在行注释掉时起作用,但可能代替函数 use$_SERVER['HTTP_USER_AGENT']【参考方案2】:

试试这个代码

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;

【讨论】:

如果这对您不起作用,您可能需要添加 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 不适用于我的图像。我确实将 CURLOPT_FOLLOWLOCATION 设置为 true。 @Abenil 添加此参数。 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); @Davinder Kumar:非常感谢,添加您的代码使上述代码有效。 欢迎您! @TrungLeNguyenNhat【参考方案3】:

正如多次提到的,要走的路是从响应头的Content-Length 字段中检索信息

但是,你应该注意

您正在探测的服务器不一定实现 HEAD 方法(!) 当 PHP 有 get_headers() 时,绝对不需要使用 fopen 或类似方法手动制作 HEAD 请求(甚至可能不支持),甚至不需要调用 curl 库(请记住:K.I.S.S. )

get_headers() 的使用遵循K.I.S.S. principle并且即使您正在探测的服务器不支持 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 标头是可选的。因此,作为一个通用解决方案它不是防弹的


【讨论】:

这应该是公认的答案。没错,Content-Length 是可选的,但它是无需下载即可获得文件大小的唯一方法 - 而get_headers 是获得content-length 的最佳方式。 请注意,这将在此 PHP 进程的所有后续 HTTP 请求中将请求方法的首选项更改为 HEAD。使用 stream_context_create 创建单独的上下文以用于调用 get_headers (7.1+)。 只是添加,如果您的 URL 或 DOCUMENT 文件名中有空格,这将返回 -1【参考方案4】:

Php 函数 get_headers() 可以让我检查 content-length

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

更多详情:PHP Function get_headers()

【讨论】:

对我来说(使用 nginx)标题是 Content-Length【参考方案5】:

当然。发出仅标头请求并查找 Content-Length 标头。

【讨论】:

【参考方案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");

【讨论】:

【参考方案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 的限制,当多个“用户”同时访问您的 get_headers 脚本时,这可能会导致一种缓慢的 loris 攻击。【参考方案8】:

最简单高效的实现方式:

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 表示“不下载文件”。此方法将文件从远程服务器加载到内存中(例如:下载)。即使服务器之间有快速连接,这也很容易超时或在大文件上花费太长时间。注意:您永远不会关闭不在全局范围内的 $fp 此功能不会尽可能长时间地下载正文;如果它包含Content-Length 标头。并且明确的$fp 关闭是不必要的;它会在过期时自动释放。 php.net/manual/en/language.types.resource.php 您可以使用nc -l localhost 8080轻松确认以上内容 实际上大多数*close函数在现代PHP中都不是必需的。它们来自两个历史原因:实现限制和模仿 C 语言。 标头不可靠,后备下载不符合 OP。最后,如果您打开一个文件,只需将其关闭即可。垃圾收集器不是懒惰的开发人员节省一行代码的借口。【参考方案9】:

由于这个问题已经被标记为“php”和“curl”,我假设你知道如何在 PHP 中使用 Curl。

如果您设置curl_setopt(CURLOPT_NOBODY, TRUE),那么您将发出 HEAD 请求,并且可能会检查响应的“Content-Length”标头,这将只是标头。

【讨论】:

【参考方案10】:

试试下面的函数来获取远程文件大小

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;

【讨论】:

这是唯一一个在 Ubuntu Linux apache 服务器上为我工作的。我确实必须在函数开始时初始化 $size 和 $status,否则按原样工作。【参考方案11】:

这是另一种适用于不支持HEAD 请求的服务器的方法。

它使用 cURL 来请求带有 HTTP 范围标头的内容,要求文件的第一个字节。

如果服务器支持范围请求(大多数媒体服务器支持),那么它将接收到资源大小的响应。

如果服务器没有响应一个字节范围,它会寻找一个 content-length 头来确定长度。

如果在范围或内容长度标头中找到大小,则中止传输。如果未找到大小并且函数开始读取响应正文,则中止传输。

如果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.";

【讨论】:

您的回答对我很有帮助。总是返回答案。即使Content-Length 不可用。 您好,感谢您的关注和评论。我真的很高兴你发现它有帮助! 这在我禁用 selinux 后对我有用。在远程图像、PDF 和 mp4 上进行了测试。 mp4 给出了结果,但“22”不是正确的文件大小。【参考方案12】:

这里的大多数答案要么使用 CURL,要么基于阅读标题。但在某些特定情况下,您可以使用更简单的解决方案。考虑filesize()'s docs on PHP.net 上的注释。你会发现一个提示:“从 PHP 5.0.0 开始,这个函数也可以与一些 URL 包装器一起使用。请参阅 Supported Protocols and Wrappers 以确定哪些包装器支持 stat() 系列功能”。

所以,如果您的服务器和 PHP 解析器配置正确,您可以简单地使用 filesize() 函数,将完整 URL 提供给它,指向您想要获取的远程文件,然后让 PHP 完成所有魔法.

【讨论】:

【参考方案13】:

试试这个:我用了,效果不错。

    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('https://***.com/questions/14679268/downloading-files-as-attachment-filesize-incorrect').PHP_EOL;

结果:

24.82 KB

912 KB

101.85 KB

【讨论】:

【参考方案14】:

为了覆盖HTTP/2请求,这里提供的函数https://***.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;

?>

【讨论】:

【参考方案15】:

如果你使用 laravel 7

use Illuminate\Support\Facades\Http;

Http::head($url)->header('Content-Length');

【讨论】:

以上是关于无需下载文件的远程文件大小的主要内容,如果未能解决你的问题,请参考以下文章

远程下载文件并设置进度显示

如何使用 iPhone SDK 获取远程文件的大小?

Android-HttpURLConnection获取下载文件大小

无需导出即可获取音频文件大小

Ftp的断点下载实现

PHP文件下载