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

时间:2011-11-04 23:40:16

标签: php http-headers mp4 jwplayer

我使用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');
?>

2 个答案:

答案 0 :(得分:1)

我使用mod_xsendfile https://tn123.org/mod_xsendfile/

让Apache处理文件服务,而不是试图在PHP中复制它们:)

答案 1 :(得分:1)

是的,它很容易做到。无需手动设置这些标头。让服务器自动完成。

这是我为视频流代理编写的工作脚本 -

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);
相关问题