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

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

【问题讨论】:

    标签: php curl


    【解决方案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://stackoverflow.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://stackoverflow.com/questions/14679268/downloading-files-as-attachment-filesize-incorrect').PHP_EOL;
            

            结果:

            24.82 KB

            912 KB

            101.85 KB

            【讨论】:

              【解决方案14】:

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

                【讨论】:

                  猜你喜欢
                  • 2011-11-02
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2019-01-20
                  • 2023-03-28
                  • 1970-01-01
                  相关资源
                  最近更新 更多