PHP를 사용하여 파일을 보낼 때 다운로드가 재개됩니까?


104

다운로드 가능한 파일의 절대 경로를 노출하고 싶지 않기 때문에 터널링 파일 다운로드에 PHP 스크립팅을 사용하고 있습니다.

header("Content-Type: $ctype");
header("Content-Length: " . filesize($file));
header("Content-Disposition: attachment; filename=\"$fileName\"");
readfile($file);

안타깝게도이 스크립트를 통해 전달 된 다운로드는 최종 사용자가 재개 할 수 없습니다.

그러한 PHP 기반 솔루션으로 재개 가능한 다운로드를 지원하는 방법이 있습니까?

답변:


102

가장 먼저해야 할 일은 Accept-Ranges: bytes모든 응답 에서 헤더 를 전송 하여 클라이언트에게 부분 콘텐츠를 지원한다는 것을 알리는 것입니다. A를 요청한다면, Range: bytes=x-y 헤더 (로 수신 x하고 y있는 번호) 당신은 클라이언트가 요청하는 범위를 구문 분석 평소와 같이 파일을 열 추구 x바이트를 미리하고 다음 보낼 y- x바이트. 또한 응답을로 설정하십시오 HTTP/1.0 206 Partial Content.

아무것도 테스트하지 않고는 어느 정도 작동 할 수 있습니다.

$filesize = filesize($file);

$offset = 0;
$length = $filesize;

if ( isset($_SERVER['HTTP_RANGE']) ) {
    // if the HTTP_RANGE header is set we're dealing with partial content

    $partialContent = true;

    // find the requested range
    // this might be too simplistic, apparently the client can request
    // multiple ranges, which can become pretty complex, so ignore it for now
    preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);

    $offset = intval($matches[1]);
    $length = intval($matches[2]) - $offset;
} else {
    $partialContent = false;
}

$file = fopen($file, 'r');

// seek to the requested offset, this is 0 if it's not a partial content request
fseek($file, $offset);

$data = fread($file, $length);

fclose($file);

if ( $partialContent ) {
    // output the right headers for partial content

    header('HTTP/1.1 206 Partial Content');

    header('Content-Range: bytes ' . $offset . '-' . ($offset + $length) . '/' . $filesize);
}

// output the regular HTTP headers
header('Content-Type: ' . $ctype);
header('Content-Length: ' . $filesize);
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Accept-Ranges: bytes');

// don't forget to send the data too
print($data);

나는 명백한 것을 놓쳤을 수도 있고, 잠재적 인 오류 원인을 가장 확실하게 무시했지만 시작해야합니다.

거기있어 여기에 일부 내용의 설명 과 나는에 대한 설명서 페이지의 일부 내용에 대한 몇 가지 정보를 찾을 FREAD를 .


3
작은 버그, 정규식은 다음과 같아야합니다. preg_match ( '/ bytes = (\ d +)-(\ d +)? /', $ _SERVER [ 'HTTP_RANGE'], $ matches)
deepwell

1
당신 말이 맞고 나는 그것을 바꿨습니다. 그러나 "bytes = xy", "bytes = -x", "bytes = x-", "bytes = xy, ab"등을 수행 할 수있는 사양에 따르면 어쨌든 너무 단순합니다. 이전 버전은 물음표가없는 것이 아니라 끝 슬래시가 누락되었습니다.
Theo

7
매우 도움이되었지만 작동하도록 두 가지 사소한 조정을해야했습니다. 1. 클라이언트가 범위 내에서 엔드 포인트를 보내지 않으면 (암시 적이므로) $length부정적입니다. $length = (($matches[2]) ? intval($matches[2]) : $filesize) - $offset;그것을 수정합니다. 2. Content-Range첫 번째 바이트를 바이트로 취급 0하므로 마지막 바이트는 $filesize - 1입니다. 따라서 ($offset + $length - 1).
Dennis

1
위의 방법은 대용량 다운로드에서 작동하지 않습니다. "PHP 치명적 오류 : 허용 된 메모리 크기 XXXX 바이트가 소진되었습니다 (XXX 바이트 할당 시도)"가 표시됩니다. 제 경우에는 100MB가 너무 컸습니다. 기본적으로 모든 파일을 변수에 저장하고 뱉어냅니다.
sarah.ferguson

1
대용량 파일 문제를 한 번에 모두 읽는 대신 청크로 읽어서 해결할 수 있습니다.
dynamichael

71

2017/01 편집 -PHP> = 7.0 https://github.com/DaveRandom/Resume 에서이를 수행하기 위해 라이브러리를 작성했습니다.

EDIT 2016/02-코드는 모 놀리 식 기능이 아닌 사용 예제 인 모듈 식 도구 세트로 완전히 다시 작성되었습니다. 아래 주석에 언급 된 수정 사항이 포함되었습니다.


몇 가지 독립 실행 형 도구 세트에서 재개 가능한 다운로드를 처리하는 테스트되고 작동하는 솔루션 (위의 Theo의 답변에 크게 기반 함). 이 코드에는 PHP 5.4 이상이 필요합니다.

이 솔루션은 여전히 ​​요청 당 하나의 범위 만 처리 할 수 ​​있지만 내가 생각할 수있는 표준 브라우저의 어떤 상황에서도 문제가되지 않습니다.

<?php

/**
 * Get the value of a header in the current request context
 *
 * @param string $name Name of the header
 * @return string|null Returns null when the header was not sent or cannot be retrieved
 */
function get_request_header($name)
{
    $name = strtoupper($name);

    // IIS/Some Apache versions and configurations
    if (isset($_SERVER['HTTP_' . $name])) {
        return trim($_SERVER['HTTP_' . $name]);
    }

    // Various other SAPIs
    foreach (apache_request_headers() as $header_name => $value) {
        if (strtoupper($header_name) === $name) {
            return trim($value);
        }
    }

    return null;
}

class NonExistentFileException extends \RuntimeException {}
class UnreadableFileException extends \RuntimeException {}
class UnsatisfiableRangeException extends \RuntimeException {}
class InvalidRangeHeaderException extends \RuntimeException {}

class RangeHeader
{
    /**
     * The first byte in the file to send (0-indexed), a null value indicates the last
     * $end bytes
     *
     * @var int|null
     */
    private $firstByte;

    /**
     * The last byte in the file to send (0-indexed), a null value indicates $start to
     * EOF
     *
     * @var int|null
     */
    private $lastByte;

    /**
     * Create a new instance from a Range header string
     *
     * @param string $header
     * @return RangeHeader
     */
    public static function createFromHeaderString($header)
    {
        if ($header === null) {
            return null;
        }

        if (!preg_match('/^\s*(\S+)\s*(\d*)\s*-\s*(\d*)\s*(?:,|$)/', $header, $info)) {
            throw new InvalidRangeHeaderException('Invalid header format');
        } else if (strtolower($info[1]) !== 'bytes') {
            throw new InvalidRangeHeaderException('Unknown range unit: ' . $info[1]);
        }

        return new self(
            $info[2] === '' ? null : $info[2],
            $info[3] === '' ? null : $info[3]
        );
    }

    /**
     * @param int|null $firstByte
     * @param int|null $lastByte
     * @throws InvalidRangeHeaderException
     */
    public function __construct($firstByte, $lastByte)
    {
        $this->firstByte = $firstByte === null ? $firstByte : (int)$firstByte;
        $this->lastByte = $lastByte === null ? $lastByte : (int)$lastByte;

        if ($this->firstByte === null && $this->lastByte === null) {
            throw new InvalidRangeHeaderException(
                'Both start and end position specifiers empty'
            );
        } else if ($this->firstByte < 0 || $this->lastByte < 0) {
            throw new InvalidRangeHeaderException(
                'Position specifiers cannot be negative'
            );
        } else if ($this->lastByte !== null && $this->lastByte < $this->firstByte) {
            throw new InvalidRangeHeaderException(
                'Last byte cannot be less than first byte'
            );
        }
    }

    /**
     * Get the start position when this range is applied to a file of the specified size
     *
     * @param int $fileSize
     * @return int
     * @throws UnsatisfiableRangeException
     */
    public function getStartPosition($fileSize)
    {
        $size = (int)$fileSize;

        if ($this->firstByte === null) {
            return ($size - 1) - $this->lastByte;
        }

        if ($size <= $this->firstByte) {
            throw new UnsatisfiableRangeException(
                'Start position is after the end of the file'
            );
        }

        return $this->firstByte;
    }

    /**
     * Get the end position when this range is applied to a file of the specified size
     *
     * @param int $fileSize
     * @return int
     * @throws UnsatisfiableRangeException
     */
    public function getEndPosition($fileSize)
    {
        $size = (int)$fileSize;

        if ($this->lastByte === null) {
            return $size - 1;
        }

        if ($size <= $this->lastByte) {
            throw new UnsatisfiableRangeException(
                'End position is after the end of the file'
            );
        }

        return $this->lastByte;
    }

    /**
     * Get the length when this range is applied to a file of the specified size
     *
     * @param int $fileSize
     * @return int
     * @throws UnsatisfiableRangeException
     */
    public function getLength($fileSize)
    {
        $size = (int)$fileSize;

        return $this->getEndPosition($size) - $this->getStartPosition($size) + 1;
    }

    /**
     * Get a Content-Range header corresponding to this Range and the specified file
     * size
     *
     * @param int $fileSize
     * @return string
     */
    public function getContentRangeHeader($fileSize)
    {
        return 'bytes ' . $this->getStartPosition($fileSize) . '-'
             . $this->getEndPosition($fileSize) . '/' . $fileSize;
    }
}

class PartialFileServlet
{
    /**
     * The range header on which the data transmission will be based
     *
     * @var RangeHeader|null
     */
    private $range;

    /**
     * @param RangeHeader $range Range header on which the transmission will be based
     */
    public function __construct(RangeHeader $range = null)
    {
        $this->range = $range;
    }

    /**
     * Send part of the data in a seekable stream resource to the output buffer
     *
     * @param resource $fp Stream resource to read data from
     * @param int $start Position in the stream to start reading
     * @param int $length Number of bytes to read
     * @param int $chunkSize Maximum bytes to read from the file in a single operation
     */
    private function sendDataRange($fp, $start, $length, $chunkSize = 8192)
    {
        if ($start > 0) {
            fseek($fp, $start, SEEK_SET);
        }

        while ($length) {
            $read = ($length > $chunkSize) ? $chunkSize : $length;
            $length -= $read;
            echo fread($fp, $read);
        }
    }

    /**
     * Send the headers that are included regardless of whether a range was requested
     *
     * @param string $fileName
     * @param int $contentLength
     * @param string $contentType
     */
    private function sendDownloadHeaders($fileName, $contentLength, $contentType)
    {
        header('Content-Type: ' . $contentType);
        header('Content-Length: ' . $contentLength);
        header('Content-Disposition: attachment; filename="' . $fileName . '"');
        header('Accept-Ranges: bytes');
    }

    /**
     * Send data from a file based on the current Range header
     *
     * @param string $path Local file system path to serve
     * @param string $contentType MIME type of the data stream
     */
    public function sendFile($path, $contentType = 'application/octet-stream')
    {
        // Make sure the file exists and is a file, otherwise we are wasting our time
        $localPath = realpath($path);
        if ($localPath === false || !is_file($localPath)) {
            throw new NonExistentFileException(
                $path . ' does not exist or is not a file'
            );
        }

        // Make sure we can open the file for reading
        if (!$fp = fopen($localPath, 'r')) {
            throw new UnreadableFileException(
                'Failed to open ' . $localPath . ' for reading'
            );
        }

        $fileSize = filesize($localPath);

        if ($this->range == null) {
            // No range requested, just send the whole file
            header('HTTP/1.1 200 OK');
            $this->sendDownloadHeaders(basename($localPath), $fileSize, $contentType);

            fpassthru($fp);
        } else {
            // Send the request range
            header('HTTP/1.1 206 Partial Content');
            header('Content-Range: ' . $this->range->getContentRangeHeader($fileSize));
            $this->sendDownloadHeaders(
                basename($localPath),
                $this->range->getLength($fileSize),
                $contentType
            );

            $this->sendDataRange(
                $fp,
                $this->range->getStartPosition($fileSize),
                $this->range->getLength($fileSize)
            );
        }

        fclose($fp);
    }
}

사용 예 :

<?php

$path = '/local/path/to/file.ext';
$contentType = 'application/octet-stream';

// Avoid sending unexpected errors to the client - we should be serving a file,
// we don't want to corrupt the data we send
ini_set('display_errors', '0');

try {
    $rangeHeader = RangeHeader::createFromHeaderString(get_request_header('Range'));
    (new PartialFileServlet($rangeHeader))->sendFile($path, $contentType);
} catch (InvalidRangeHeaderException $e) {
    header("HTTP/1.1 400 Bad Request");
} catch (UnsatisfiableRangeException $e) {
    header("HTTP/1.1 416 Range Not Satisfiable");
} catch (NonExistentFileException $e) {
    header("HTTP/1.1 404 Not Found");
} catch (UnreadableFileException $e) {
    header("HTTP/1.1 500 Internal Server Error");
}

// It's usually a good idea to explicitly exit after sending a file to avoid sending any
// extra data on the end that might corrupt the file
exit;

여기에 꽤 좋은 코드가 있습니다. $ length가 설정된 줄에서 버그를 찾았습니다. 이어야합니다 : $ length = $ end-$ start + 1;
bobwienholt

다운로드를 어떻게 일시 중지합니까
Prasanth Bendra 2013

3
Content-Length를 실제 파일 크기로 설정해야합니까, 아니면 전송되는 부분 바이트 수로 설정해야합니까? 이 페이지는 부분 바이트 여야하는 것처럼 보이지만 위의 예제 코드에서 수행 된 작업이 아닙니다. w3.org/Protocols/rfc2616/rfc2616-sec14.html
willus

3
또 다른 작은 오타 : $start = $end - intval($range[0]);이어야합니다range[1]
BurninLeo 2014 년

1
@ sarah.ferguson 코드가 완전히 다시 작성되고 업데이트되었습니다. 위를 참조하십시오.
DaveRandom

16

이것은 내가 그것을 사용하고 있고 더 이상 문제가없는 것을 100 % 슈퍼 체크합니다.

        /* Function: download with resume/speed/stream options */


         /* List of File Types */
        function fileTypes($extension){
            $fileTypes['swf'] = 'application/x-shockwave-flash';
            $fileTypes['pdf'] = 'application/pdf';
            $fileTypes['exe'] = 'application/octet-stream';
            $fileTypes['zip'] = 'application/zip';
            $fileTypes['doc'] = 'application/msword';
            $fileTypes['xls'] = 'application/vnd.ms-excel';
            $fileTypes['ppt'] = 'application/vnd.ms-powerpoint';
            $fileTypes['gif'] = 'image/gif';
            $fileTypes['png'] = 'image/png';
            $fileTypes['jpeg'] = 'image/jpg';
            $fileTypes['jpg'] = 'image/jpg';
            $fileTypes['rar'] = 'application/rar';

            $fileTypes['ra'] = 'audio/x-pn-realaudio';
            $fileTypes['ram'] = 'audio/x-pn-realaudio';
            $fileTypes['ogg'] = 'audio/x-pn-realaudio';

            $fileTypes['wav'] = 'video/x-msvideo';
            $fileTypes['wmv'] = 'video/x-msvideo';
            $fileTypes['avi'] = 'video/x-msvideo';
            $fileTypes['asf'] = 'video/x-msvideo';
            $fileTypes['divx'] = 'video/x-msvideo';

            $fileTypes['mp3'] = 'audio/mpeg';
            $fileTypes['mp4'] = 'audio/mpeg';
            $fileTypes['mpeg'] = 'video/mpeg';
            $fileTypes['mpg'] = 'video/mpeg';
            $fileTypes['mpe'] = 'video/mpeg';
            $fileTypes['mov'] = 'video/quicktime';
            $fileTypes['swf'] = 'video/quicktime';
            $fileTypes['3gp'] = 'video/quicktime';
            $fileTypes['m4a'] = 'video/quicktime';
            $fileTypes['aac'] = 'video/quicktime';
            $fileTypes['m3u'] = 'video/quicktime';
            return $fileTypes[$extention];
        };

        /*
          Parameters: downloadFile(File Location, File Name,
          max speed, is streaming
          If streaming - videos will show as videos, images as images
          instead of download prompt
         */

        function downloadFile($fileLocation, $fileName, $maxSpeed = 100, $doStream = false) {
            if (connection_status() != 0)
                return(false);
        //    in some old versions this can be pereferable to get extention
        //    $extension = strtolower(end(explode('.', $fileName)));
            $extension = pathinfo($fileName, PATHINFO_EXTENSION);

            $contentType = fileTypes($extension);
            header("Cache-Control: public");
            header("Content-Transfer-Encoding: binary\n");
            header('Content-Type: $contentType');

            $contentDisposition = 'attachment';

            if ($doStream == true) {
                /* extensions to stream */
                $array_listen = array('mp3', 'm3u', 'm4a', 'mid', 'ogg', 'ra', 'ram', 'wm',
                    'wav', 'wma', 'aac', '3gp', 'avi', 'mov', 'mp4', 'mpeg', 'mpg', 'swf', 'wmv', 'divx', 'asf');
                if (in_array($extension, $array_listen)) {
                    $contentDisposition = 'inline';
                }
            }

            if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")) {
                $fileName = preg_replace('/\./', '%2e', $fileName, substr_count($fileName, '.') - 1);
                header("Content-Disposition: $contentDisposition;
                    filename=\"$fileName\"");
            } else {
                header("Content-Disposition: $contentDisposition;
                    filename=\"$fileName\"");
            }

            header("Accept-Ranges: bytes");
            $range = 0;
            $size = filesize($fileLocation);

            if (isset($_SERVER['HTTP_RANGE'])) {
                list($a, $range) = explode("=", $_SERVER['HTTP_RANGE']);
                str_replace($range, "-", $range);
                $size2 = $size - 1;
                $new_length = $size - $range;
                header("HTTP/1.1 206 Partial Content");
                header("Content-Length: $new_length");
                header("Content-Range: bytes $range$size2/$size");
            } else {
                $size2 = $size - 1;
                header("Content-Range: bytes 0-$size2/$size");
                header("Content-Length: " . $size);
            }

            if ($size == 0) {
                die('Zero byte file! Aborting download');
            }
            set_magic_quotes_runtime(0);
            $fp = fopen("$fileLocation", "rb");

            fseek($fp, $range);

            while (!feof($fp) and ( connection_status() == 0)) {
                set_time_limit(0);
                print(fread($fp, 1024 * $maxSpeed));
                flush();
                ob_flush();
                sleep(1);
            }
            fclose($fp);

            return((connection_status() == 0) and ! connection_aborted());
        }

        /* Implementation */
        // downloadFile('path_to_file/1.mp3', '1.mp3', 1024, false);

1
속도 제한이 정말 유용하기 때문에 찬성했지만 재개 된 파일 (Firefox)에 대한 MD5 검사에서 불일치가 표시되었습니다. $ range의 str_replace가 잘못되었습니다. 또 다른 폭발, 결과가 숫자로 만들어지고 Content-Range 헤더에 대시가 추가되어야합니다.
WhoIsRich 2013 년

원격 파일 다운로드를 지원하도록 사용자 정의하는 방법은 무엇입니까?
Siyamak Shahpasand 2013 년

1
'Content-Type : $ contentType'을 큰 따옴표로 묶으려고했습니다.
Matt

set_time_limit (0); 제 의견으로는 적절하지 않습니다. 더 합리적인 24 시간 제한이 아닐까요?
twicejr 15.04.20

내 오타를 확인 해주셔서 감사합니다 :)!
user1524615

15

예. 바이트 범위를 지원합니다. RFC 2616 섹션 14.35를 참조하십시오 .

기본적으로 Range헤더를 읽고 지정된 오프셋에서 파일 제공을 시작 해야 함을 의미합니다 .

이것은 전체 파일을 제공하기 때문에 readfile ()을 사용할 수 없음을 의미합니다. 대신 fopen ()을 먼저 사용한 다음 fseek () 를 올바른 위치에 사용한 다음 fpassthru () 를 사용하여 파일을 제공하십시오.


4
fpassthru는 파일이 여러 메가 바이트 인 경우 좋은 생각이 아닙니다. 메모리가 부족할 수 있습니다. 그냥 fread ()와 print ()를 덩어리로하세요.
Willem

3
fpassthru는 수백 메가 바이트로 훌륭하게 작동합니다. echo file_get_contents(...)작동하지 않았습니다 (OOM). 그래서 나는 그것이 문제라고 생각하지 않습니다. PHP 5.3.
Janus Troelsen 2011 년

1
@JanusTroelsen 아니요, 아닙니다. 그것은 모두 서버의 구성에 달려 있습니다. PHP에 많은 메모리가 할당 된 강력한 서버가 있다면 잘 작동 할 것입니다. "약한"구성 (말 그대로 : 공유 호스팅)에서는 fpassthru50MB 파일에서도 사용 이 실패합니다. 약한 서버 구성에서 대용량 파일을 제공하는 경우 반드시 사용하지 않아야합니다. @Wimmer가 올바르게 지적했듯이 fread+ print가이 경우에 필요한 모든 것입니다.
trejder 2014

2
@trejder : readfile ()에 대한 참고 사항 참조하십시오 . readfile ()은 대용량 파일을 보낼 때도 자체적으로 메모리 문제를 나타내지 않습니다. 메모리 부족 오류가 발생하면 ob_get_level ()을 사용하여 출력 버퍼링이 꺼져 있는지 확인하십시오.
Janus Troelsen 2014

1
@trejder 문제는 출력 버퍼링을 올바르게 구성하지 않았다는 것입니다. : 그것은 당신이 그것을 말할 경우, 자동으로 청크 않습니다 php.net/manual/en/... 예를 들어, output_buffering을 = 4096 (당신의 틀이를 허용하지 않는 경우, 당신이 프레임 워크는 짜증)
ZJR

11

PHP 코드를 "자신 만의"롤링하지 않고도이 문제를 해결할 수있는 정말 좋은 방법은 mod_xsendfile Apache 모듈을 사용하는 것입니다. 그런 다음 PHP에서 적절한 헤더를 설정하기 만하면됩니다. 아파치는 그 일을합니다.

header("X-Sendfile: /path/to/file");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; file=\"filename\"");

2
파일을 보낸 후 연결을 해제하려면 어떻게해야합니까?
Janus Troelsen 2011 년

1
파일을 보낸 후 링크를 ​​해제하려면이를 나타내는 특수 플래그가 필요합니다 XSendFilePath <absolute path> [AllowFileDelete]( tn123.org/mod_xsendfile/beta ).
옌스 A. 코흐

9

새 PECL 모듈을 설치하려는 경우 PHP재개 가능한 다운로드를 지원 하는 가장 쉬운 방법 은 다음 http_send_file()과 같습니다.

<?php
http_send_content_disposition("document.pdf", true);
http_send_content_type("application/pdf");
http_throttle(0.1, 2048);
http_send_file("../report.pdf");
?>

출처 : http://www.php.net/manual/en/function.http-send-file.php

우리는 데이터베이스에 저장된 콘텐츠를 제공하는 데 사용하며 매력처럼 작동합니다!


3
매력처럼 작동합니다. 그러나 출력 버퍼링 (ob_start 등)이 켜져 있지 않도록주의하십시오. 특히 대용량 파일을 보낼 때 전체 요청 범위를 버퍼링합니다.
Pieter van Ginkel 2013 년

이것이 PHP에 언제 추가 되었습니까? 항상 거기 있었나요?
thomthom

1
그것은 PHP가 아니라 Pecl입니다. 이 기능이 없습니다.
지오

4

최고의 답변에는 다양한 버그가 있습니다.

  1. 주요 버그 : Range 헤더를 올바르게 처리하지 않습니다. bytes a-b의미한다 [a, b]대신 [a, b)하고, bytes a-처리되지 않습니다.
  2. 사소한 버그 : 출력을 처리하기 위해 버퍼를 사용하지 않습니다. 이로 인해 메모리가 너무 많이 소모되고 대용량 파일의 경우 속도가 느려질 수 있습니다.

수정 된 코드는 다음과 같습니다.

// TODO: configurations here
$fileName = "File Name";
$file = "File Path";
$bufferSize = 2097152;

$filesize = filesize($file);
$offset = 0;
$length = $filesize;
if (isset($_SERVER['HTTP_RANGE'])) {
    // if the HTTP_RANGE header is set we're dealing with partial content
    // find the requested range
    // this might be too simplistic, apparently the client can request
    // multiple ranges, which can become pretty complex, so ignore it for now
    preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
    $offset = intval($matches[1]);
    $end = $matches[2] || $matches[2] === '0' ? intval($matches[2]) : $filesize - 1;
    $length = $end + 1 - $offset;
    // output the right headers for partial content
    header('HTTP/1.1 206 Partial Content');
    header("Content-Range: bytes $offset-$end/$filesize");
}
// output the regular HTTP headers
header('Content-Type: ' . mime_content_type($file));
header("Content-Length: $filesize");
header("Content-Disposition: attachment; filename=\"$fileName\"");
header('Accept-Ranges: bytes');

$file = fopen($file, 'r');
// seek to the requested offset, this is 0 if it's not a partial content request
fseek($file, $offset);
// don't forget to send the data too
ini_set('memory_limit', '-1');
while ($length >= $bufferSize)
{
    print(fread($file, $bufferSize));
    $length -= $bufferSize;
}
if ($length) print(fread($file, $length));
fclose($file);

왜 이것이 필요 ini_set('memory_limit', '-1');합니까?
Mikko Rantalainen

1
@MikkoRantalainen 나는 잊었다. 그것을 제거하고 어떤 일이 일어나는지 볼 수 있습니다.
Mygod

1
안타깝게도 $ matches [2]가 설정되지 않은 경우 (예 : "Range = 0-"요청) $ end 할당에서 오류가 발생합니다. 나는이 대신 사용 :if(!isset($matches[2])) { $end=$fs-1; } else { $end = intval($matches[2]); }
스카이 넷

3

예,이를 위해 Range 헤더를 사용할 수 있습니다. 전체 다운로드를 위해 클라이언트에 3 개의 헤더를 더 제공해야합니다.

header ("Accept-Ranges: bytes");
header ("Content-Length: " . $fileSize);
header ("Content-Range: bytes 0-" . $fileSize - 1 . "/" . $fileSize . ";");

중단 된 다운로드보다 다음을 수행하여 Range 요청 헤더를 확인해야합니다.

$headers = getAllHeaders ();
$range = substr ($headers['Range'], '6');

이 경우 206 상태 코드로 콘텐츠를 제공하는 것을 잊지 마십시오.

header ("HTTP/1.1 206 Partial content");
header ("Accept-Ranges: bytes");
header ("Content-Length: " . $remaining_length);
header ("Content-Range: bytes " . $start . "-" . $to . "/" . $fileSize . ";");

요청 헤더에서 $ start 및 $ to 변수를 가져오고 fseek ()를 사용하여 파일에서 올바른 위치를 찾습니다.


2
@ceejayoz : getallheaders ()는 apache uk2.php.net/getallheaders를
Tom Haigh



1

HTTP에서 다운로드 재개는 Range헤더를 통해 수행됩니다 . 요청이 포함되어있는 경우 Range헤더를, 그리고 다른 지표 (예를 들어 경우 If-Match, If-Unmodified-Since)의 범위를 표시, 내용이 다운로드가 시작된 이후, 당신은 206 응답 코드를 (오히려 200보다) 줄 변경되지 않았 음을 나타냅니다 당신은 반환하고 바이트 에 Content-Range헤더 다음 반응 체에 그 범위를 제공한다.

그래도 PHP에서 어떻게하는지 모르겠습니다.


1

감사합니다 Theo! divx 플레이어가 bytes = 9932800-

근데 그렇게하는 방법을 보여 주니까 감사합니다 : D

if(isset($_SERVER['HTTP_RANGE']))
{
    file_put_contents('showrange.txt',$_SERVER['HTTP_RANGE']);

0

모든 브라우저에서 바이트 범위 요청 지원을 위해 아래 코드를 사용할 수 있습니다.

    <?php
$file = 'YouTube360p.mp4';
$fileLoc = $file;
$filesize = filesize($file);
$offset = 0;
$fileLength = $filesize;
$length = $filesize - 1;

if ( isset($_SERVER['HTTP_RANGE']) ) {
    // if the HTTP_RANGE header is set we're dealing with partial content

    $partialContent = true;
    preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);

    $offset = intval($matches[1]);
    $tempLength = intval($matches[2]) - 0;
    if($tempLength != 0)
    {
        $length = $tempLength;
    }
    $fileLength = ($length - $offset) + 1;
} else {
    $partialContent = false;
    $offset = $length;
}

$file = fopen($file, 'r');

// seek to the requested offset, this is 0 if it's not a partial content request
fseek($file, $offset);

$data = fread($file, $length);

fclose($file);

if ( $partialContent ) {
    // output the right headers for partial content
    header('HTTP/1.1 206 Partial Content');
}

// output the regular HTTP headers
header('Content-Type: ' . mime_content_type($fileLoc));
header('Content-Length: ' . $fileLength);
header('Content-Disposition: inline; filename="' . $file . '"');
header('Accept-Ranges: bytes');
header('Content-Range: bytes ' . $offset . '-' . $length . '/' . $filesize);

// don't forget to send the data too
print($data);
?>
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.