为什么在使用 HTTP Accept-Ranges 服务大文件时不满足 PHP "memory_limit" 和 "max_execution_time" 限制?

Why are PHPs "memory_limit" and "max_execution_time" limitations not met when using HTTP Accept-Ranges for serving large files?

以下脚本用于(评估)向客户端发送大型视频文件。它在后面使用 http headers Accept-Ranges。即使在处理大文件(> 2 GB)时,也不会满足 PHP 限制(为了测试,我设置了较小的值,例如 memory_limit=16MB 和 max_execution_time=30)。

我想“理解”后面的上下文,因为chrome只显示一个(部分)请求,每隔几秒增加“时间”和“大小”,尽管没有显示其他请求在 apache 日志文件中。

$file = './videos/' . basename($_GET['video']);

if(!file_exists($file)) return; 

$fp = @fopen($file, 'rb');      
$size   = filesize($file); // File size 
$length = $size;           // Content length
$start  = 0;               // Start byte
$end    = $size - 1;       // End byte  

header('Content-type: video/mp4');
header("Accept-Ranges: 0-$length");
header("Accept-Ranges: bytes"); 

if (isset($_SERVER['HTTP_RANGE'])) {
    $c_start = $start;              
    $c_end   = $end;                
    list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
    if (strpos($range, ',') !== false) {
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        header("Content-Range: bytes $start-$end/$size");
        exit;      
    }              
    if ($range == '-') {            
        $c_start = $size - substr($range, 1);
    }else{         
        $range  = explode('-', $range); 
        $c_start = $range[0];           
        $c_end   = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
    }
    $c_end = ($c_end > $end) ? $end : $c_end;
    if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) { 
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        header("Content-Range: bytes $start-$end/$size");
        exit;      
    }
    $start  = $c_start;             
    $end    = $c_end;               
    $length = $end - $start + 1;    
    fseek($fp, $start);             
    header('HTTP/1.1 206 Partial Content');
}   
               
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: ".$length);

$buffer = 1024 * 8;
while(!feof($fp) && ($p = ftell($fp)) <= $end) { 
    if ($p + $buffer > $end) {      
        $buffer = $end - $p + 1;        
    }              
    set_time_limit(0);              
    echo fread($fp, $buffer);       
    ob_flush();    
}
fclose($fp);       
exit();

通过使用 curl 不带任何范围 headers 的命令行请求脚本,同时具有 max_execution_time=1 并提供 2.893 GB 的文件,从而找出更多详细信息。

在同一台机器上(在 7 秒和 678 MB 时中止):

% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed  
23 2893M   23  678M    0     0  92.5M      0  0:00:31  0:00:07  0:00:24     0 
curl: (18) transfer closed with 2322893144 bytes remaining to read

在带宽较低的外国机器上(在 49 秒和 599 MB 时中止):

% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed  
20 2893M   20  599M    0     0  12.1M      0  0:03:58  0:00:49  0:03:09 10284 
curl: (18) transfer closed with 2404747608 bytes remaining to read

关于这一点,max_execution_time=30 在这种情况下并不是真正的“小”,因为它允许传输估计大约 30 * 600 MB(= 18 GB!)。客户端的请求持续时间与服务器端的执行时间完全无关。

事实上,在 html5 视频 object 中调用脚本时,有几个部分请求完成了 max_execution_time=1 (在桌面客户端上,每个请求的带宽甚至更低大约需要 14 分钟,直到中止并传输大约 500 MB)。

最后,我只是感到困惑,因为客户端请求持续时间和服务器端执行时间之间存在如此巨大的差异。