如何通过php脚本提供文件并使其与文件的直接链接完全相同?

问题描述:

我使用php脚本检查用户是否在提供图像或视频之前登录。实际文件存储在无法直接访问的文件夹中。如果认证成功,php脚本将中继/输出文件。我的目标是让通过php脚本提供的文件尽可能接近实际文件的直接链接。如何通过php脚本提供文件并使其与文件的直接链接完全相同?

所以,这是交易。图像工作正常。视频(MP4)的工作有一些注意事项。我无法使用h264.code-shop.com流式传输模块进行伪流式传输,并且视频只能在iPhone上成功播放一次。一旦视频到达结尾,我无法刷新视频而无需刷新页面,并收到“视频无法加载”错误(JW播放器)。如果我绕过PHP脚本并直接链接到视频文件,一切正常。因此,显然我的php脚本生成的输出与通常从直接访问文件获得的输出之间有某些不同。所以,对于那里的所有专家,我可能会错过什么?正确的http头文件?我能做些什么来使我的脚本输出文件的方式与直接访问文件的方式完全相同?

下面是我使用的脚本:

<?php 

if (!isset($_GET['f'])){die(header('location:../login.php'));} 
if (!isset($_GET['onlyHappensFromHTACCESS'])) { 
$_GET['f'] = "../protectedFolder/".$_GET['f']; 
$file = realpath($_GET['f']); 
$type = getFileType($file); 
if (acceptableType($type)) 
{ 
if (goodTiming()) 
    { 
    //this function used to allow navigation away from the page while video has not completely loaded 
    session_write_close(); 

    $fs = stat($file); 

    header("Content-Type: $type"); 
    header("Etag: ".sprintf('"%x-%x-%s"', $fs['ino'], $fs['size'],base_convert(str_pad($fs['mtime'],16,"0"),10,16))); 

    if (isset($_SERVER['HTTP_RANGE'])) 
    { // do it for any device that supports byte-ranges not only iPhone 
    rangeDownload($file); 
    } 
    else 
    { 
    $size = filesize($file); // File size 

    header("Content-Length: $size"); 
    header("Last-Modified: " .gmdate("D, d M Y H:i:s")." GMT"); 
    header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0"); 
    header("Pragma: no-cache"); 
    header("Keep-Alive: timeout=5, max=100"); 
    header("Connection: Keep-Alive"); 

    $fh = fopen($file, "rb"); 

    while (($buf=fread($fh, 1024 * 8)) != '') 
    { 
    set_time_limit(0); // Reset time limit for big files 
    echo $buf; 
    flush(); 
    } 

    fclose($fh); 
    } 
    } 
die(); 
} 
header('HTTP/1.1 403 Forbidden'); 
die(header('location:../login.php')); 
} 

function getFileType($file) { 
if (function_exists("finfo_open")) { 
$finfo = finfo_open(FILEINFO_MIME_TYPE); 
if ($file==false){$file=realpath("../authorization_failure.html");} 
$type = finfo_file($finfo, $file); 
finfo_close($finfo); 
return $type; 
} 
else { 
$types = array(
    'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'pjpeg' => 'image/jpeg', 'png' => 'image/png', 
    'gif' => 'image/gif', 'bmp' => 'image/bmp', 'flv' => 'video/x-flv', 'mp4' => 'video/mp4' 
); 
$ext = substr($file, strrpos($file, '.') + 1); 
if (key_exists($ext, $types)) return $types[$ext]; 
return "unknown"; 
} 
} 


function acceptableType($type) { 
$array = array("image/jpeg", "image/jpg", "image/png", "image/png", "video/x-flv", "video/mp4"); 
if (in_array($type, $array)) 
    return true; 
return false; 
} 


function goodTiming() { 
$n = time(); 
session_start(); 
if ($n - $_SESSION['lastcheck'] > 15) 
    return false; 
return true; 
} 

function rangeDownload($file) { 

$fp = @fopen($file, 'rb'); 

$size = filesize($file); // File size 
$length = $size;   // Content length 
$start = 0;    // Start byte 
$end = $size - 1;  // End byte 
// Now that we've gotten so far without errors we send the accept range header 
/* At the moment we only support single ranges. 
* Multiple ranges requires some more work to ensure it works correctly 
* and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2 
* 
* Multirange support annouces itself with: 
* header('Accept-Ranges: bytes'); 
* 
* Multirange content must be sent with multipart/byteranges mediatype, 
* (mediatype = mimetype) 
* as well as a boundry header to indicate the various chunks of data. 
*/ 
header("Accept-Ranges: 0-$length"); 
// header('Accept-Ranges: bytes'); 
// multipart/byteranges 
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2 
if (isset($_SERVER['HTTP_RANGE'])) { 

    $c_start = $start; 
    $c_end = $end; 
    // Extract the range string 
    list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2); 
    // Make sure the client hasn't sent us a multibyte range 
    if (strpos($range, ',') !== false) { 

     // (?) Shoud this be issued here, or should the first 
     // range be used? Or should the header be ignored and 
     // we output the whole content? 
     header('HTTP/1.1 416 Requested Range Not Satisfiable'); 
     header("Content-Range: bytes $start-$end/$size"); 
     // (?) Echo some info to the client? 
     exit; 
    } 
    // If the range starts with an '-' we start from the beginning 
    // If not, we forward the file pointer 
    // And make sure to get the end byte if spesified 
    if ($range== '-') { 

     // The n-number of the last bytes is requested 
     $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; 
    } 
    /* Check the range and make sure it's treated according to the specs. 
    * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html 
    */ 
    // End bytes cannot be larger than $end. 
    $c_end = ($c_end > $end) ? $end : $c_end; 
    // Validate the requested range and return an error if it's not correct. 
    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"); 
     // (?) Echo some info to the client? 
     exit; 
    } 
    $start = $c_start; 
    $end = $c_end; 
    $length = $end - $start + 1; // Calculate new content length 
    fseek($fp, $start); 
    header('HTTP/1.1 206 Partial Content'); 
} 
// Notify the client the byte range we'll be outputting 
header("Content-Range: bytes $start-$end/$size"); 
header("Content-Length: $length"); 

// Start buffered download 
$buffer = 1024 * 8; 
while(!feof($fp) && ($p = ftell($fp)) <= $end) { 

    if ($p + $buffer > $end) { 

     // In case we're only outputtin a chunk, make sure we don't 
     // read past the length 
     $buffer = $end - $p + 1; 
    } 
    set_time_limit(0); // Reset time limit for big files 
    echo fread($fp, $buffer); 
    flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit. 
} 

fclose($fp); 

} 


header('location:../login.php'); 
?> 

我用mod_xsendfile这个 https://tn123.org/mod_xsendfile/

让Apache的处理服务的文件,而不是试图复制这一切在PHP :)

+0

,而是参考htaccess的建议可能是... – barryhunter

+0

Xsendfile无疑简化我的代码并可能增加我的网站的效率。但是,使用xsendfile时,我的原始脚本仍存在两个相同的问题。也就是说,伪流式传输不起作用,思维者只会在抛出错误之前通过一次播放视频。您是否遇到过有关设置的任何问题?任何解决这些问题的方法?绝对是一个有用的建议,虽然和现场关于我使用Apache,所以谢谢。 – Jeff

是的,它很容易做到。不需要手动设置这些标题。让服务器自动执行。

继承人,我写了一个视频流媒体代理工作的脚本 -

ini_set('memory_limit','1024M'); 

set_time_limit(3600); 

ob_start(); 

**// do any user checks here - authentication/ip restriction/max downloads/whatever** 

**// if check fails, return back error message** 

**// if check succeeds, proceed with code below** 

if(isset($_SERVER['HTTP_RANGE'])) 

$opts['http']['header']="Range: ".$_SERVER['HTTP_RANGE']; 

$opts['http']['method']= "HEAD"; 

$conh=stream_context_create($opts); 

$opts['http']['method']= "GET"; 

$cong= stream_context_create($opts); 

$out[]= file_get_contents($real_file_location_path_or_url,false,$conh); 

$out[]= $http_response_header; 

ob_end_clean(); 

array_map("header",$http_response_header); 

readfile($real_file_location_path_or_url,false,$cong); 
当然我不actully你使用Apache知道