<?php
// TODO: 权限判断
// 文件
$file = __DIR__ . '/numbers.txt';
$content_type = 'text/plain';
// 检查是否可读,并得到文件的大小
if (($file_length = filesize($file)) === false)
{
error_log("Problem reading filesize of $file.");
}
// 解析首部来确定发送响应所需的信息
if (isset($_SERVER['HTTP_RANGE']))
{
// 定界符不区分大小写
if ( ! preg_match('/bytes=\d*-\d*(,\d*-\d*)*$/i', $_SERVER['HTTP_RANGE']))
{
error_log("Client requestd invalid Range.");
send_error($file_length);
exit;
}
/*
* 规范: “客户在一个请求多个字节范围(byte-ranges)时,服务器应当按它们在请求中出现的顺序返回这些范围。”
*/
$ranges = explode(',', substr($_SERVER['HTTP_RANGE'], 6)); // 字节 = 后面的所有内容
$offsets = array();
// 抽取和验证每个部分
// 只保存通过验证的部分
foreach($ranges as $range)
{
$offset = parse_offset($range, $file_length);
if ($offset !== false)
{
$offsets[] = $offset;
}
}
/*
* 取决于所请求的合法范围的个数,必须采用不同的格式返回响应
*/
switch (count($offsets))
{
case 0:
// 非合法范围
error_log("Client requested no valid ranges.");
send_error($file_length);
exit;
break;
case 1:
// 一个合法阀内,发送标准应答
http_response_code(206); // 部分内容
list($start, $end) = $offsets[0];
header("Content-Range: bytes $start-$end/$file_length");
header("Content-Type: $content_type");
// 设置变量,从而可以在这里以及下一个情况中重用代码(为什么我觉得这是一个很蠢的注意 ==!)
// 注意: 0-0为1字节,因为范围包含其两个端点
$content_length = $end - $start + 1;
$boundaries = array(0 => '', 1 => '');
break;
default:
// 多个合法范围,发送多部分应答
http_response_code(206); // 部分应答
$boundary = str_rand(32); // 分隔各个部分的字符串
/*
* 需要计算整个响应的内容长度(Content-Length),不过将整个响应加载到一个字符串中占用大量的内存就,所以使用偏移量计算值。另外利用这个机会计算边界。
*/
$boundaries = array();
$content_length = 0;
foreach ($offsets as $offset)
{
list($start, $end) = $offset;
// 用于分解各个部分
$boundary_header =
"\r\n" .
"--$boundary\r\n" .
"Content-Type: $content_type\r\n" .
"Content-Range: bytes $start - $end / $file_length\r\n" .
"\r\n";
$content_length += strlen($boundary_header) + ($end - $start + 1);
$boundaries[] = $boundary_header;
}
// 增加结束边界
$boundary_footer = "\r\n--$boundary--";
$content_length += strlen($boundary_footer);
$boundaries[] = $boundary_footer;
// 去除第一个边界中多余的 \r\n
$boundaries[0] = substr($boundaries[0], 2);
$content_length -= 2;
// 改为特殊的多部分内容类型(Content-Type)
$content_type = "multipart/byteranges; boundary= $boundary";
}
}
else
{
// 发送整个文件
// 设置变量, 就好像这是从 Range 首部抽取的
$start = 0;
$end = $file_length - 1;
$offset = array($start, $end);
$offsets = array($offset);
$content_length = $file_length;
$boundaries = array(0 => '', 1 => '');
}
// 指出得到的是什么
header("Content-Type: $content_type"); // 前面已经指定过了,为什么这里还要指定?
header("Content-Length: $content_length");
// 提供给用户
$handle = fopen($file, 'r');
if ($handle)
{
$offsets_count = count($offsets);
// 输出各个定界符和文件适当的部分
for($i = 0; $i < $offsets_count; $i++)
{
echo $boundaries[$i];
list($start, $end) = $offsets[$i];
send_range($handle, $start, $end);
}
// 结束边界
echo $boundaries[$i - 1];
fclose($handle);
}
else
{
error_log("Error: fopen() fail.");
}
// 在文件中移动适当的位置
// 按块输出所请求的部分
function send_range($handle, $start, $end)
{
$line_length = 4096; // 魔法数
if(fseek($handle, $start) === -1)
{
error_log("Error:fseek() fail.");
}
$left_to_read = $end - $start + 1;
do{
$length = min($line_length, $left_to_read);
if (($buffer = fread($handle, $length)) !== false)
{
echo $buffer;
}
else
{
error_log("Error: fread() fail.");
}
}while($left_to_read -= $length);
}
// 发送首部失败
function send_error($file_length)
{
http_response_code(416);
header("Content-Range: bytes */$file_length");
}
// 将一个偏移量转换为开始和结束位置,如果偏移量是非法的返回 false
function parse_offset($range, $file_length)
{
/*
* 规范: “字节范围(byte-range-spec)中的首字节位置(first-byte-pos)值指定了范围中第一个字节的字节偏移量”
* "末字节位置(last-byte-pos)值指定了范围中最后一个字节的字节偏移量,也就是说,指定的字节位置包含在范围内"
*/
list($start, $end) = explode('-', $range);
/*
*
*/
if ($start === '')
{
if ($end === '' || $end === 0)
{
return false;
}
else
{
/*
* 规范: "如果实体比指定的后缀长度(suffix-length)短,则使用整个实体体(entity-body)"
*/
$start = max(0, $file_length - $end);
$end = $file_length - 1;
}
}
else
{
/*
* 规范: "如果没有提供末字节位置(last-byte-pos)值,或者如果这个值大于或等于实体体的当前长度,末字节位置则等于实体体当前长度减1"
*/
if ($end === '' || $end > $file_length - 1)
{
$end = $file_length - 1;
}
/*
* 规范: "如果提供了末字节位置值,它必须大于或等于字节阀内规范中的首字节,否则在语法中就是不合法的"
*
*/
if ($start > $end)
{
return false;
}
}
return array($start, $end);
}
// 生成一个随机字符串来分割响应中的各个部分
function str_rand($length = 32, $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
if ( ! is_int($length) || $length < 0)
{
return false;
}
$characters_length = strlen($characters) - 1;
$string = '';
for($i = $length; $i > 0; $i--)
{
$string .= $characters[mt_rand(0, $characters_length)];
}
return $string;
}
http range
最新推荐文章于 2024-07-09 23:48:38 发布