http range

来源:互联网 发布:linux 没有service 编辑:程序博客网 时间:2024/05/22 01:39

HTTP RANGE 流程图

<?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");}// 将一个偏移量转换为开始和结束位置,如果偏移量是非法的返回 falsefunction 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;}
0 0