Відновлювані завантаження при використанні 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заголовок у всіх відповідях, щоб повідомити клієнту, що ви підтримуєте частковий вміст. Потім, якщо 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'], $
match

1
Ти маєш рацію, і я це змінив. Однак я все одно занадто спрощений, відповідно до специфікацій ви можете робити "байт = xy", "байт = -x", "байт = x-", "байт = xy, ab" і т.д., тому помилка в попередня версія була відсутнім кінцевим косою рисою, а не відсутністю знака питання.
Тео

7
Дуже корисно, але мені довелося зробити два незначні зміни, щоб він працював: 1. Якщо клієнт не надішле кінцеву точку в діапазоні (оскільки це неявно), $lengthбуде негативним. $length = (($matches[2]) ? intval($matches[2]) : $filesize) - $offset;виправляє це. 2. Content-Rangeтрактує перший байт як байт 0, тому останній байт є $filesize - 1. Тому так і має бути ($offset + $length - 1).
Денніс

1
Вище не працює для великих завантажень, ви отримуєте "Фатальна помилка PHP: Дозволений розмір пам'яті в XXXX байт вичерпано (намагався виділити XXX байт)". У моєму випадку 100 Мб було занадто великим. Ви в основному зберігаєте весь файл у змінній і випилюєте його.
sarah.ferguson

1
Ви можете вирішити проблему з великим файлом, прочитавши його в шматки замість усіх відразу.
динамічний шель

71

EDIT 2017/01 - я написав бібліотеку для цього в PHP> = 7.0 https://github.com/DaveRandom/Resume

EDIT 2016/02 - Кодекс повністю переписав набір модульних інструментів як приклад використання, а не монолітну функцію. Виправлення, зазначені в коментарях нижче, були включені.


Тестоване, робоче рішення (яке базується на відповіді Тео вище), яке стосується відновлюваних завантажень, у наборі кількох автономних інструментів. Цей код вимагає 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

Як я призупиняю завантаження
Прасант Бендра

3
Чи слід встановлювати довжину вмісту на фактичний розмір файлу чи просто на кількість відправлених часткових байтів? На цій сторінці виглядає так, що це повинні бути часткові байти, але це не те, що зроблено в наведеному вище прикладі коду. w3.org/Protocols/rfc2616/rfc2616-sec14.html
willus

3
Ще одна маленька помилка: $start = $end - intval($range[0]);має бутиrange[1]
BurninLeo

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
Я схвалив, оскільки обмеження швидкості дуже корисне, проте перевірка MD5 відновленого файлу (Firefox) показала невідповідність. Строка str_replace для $ діапазону помилкова, має бути ще один вибух, результат отриманий числовим числом та тире додано до заголовка контенту.
WhoIsRich

Як налаштувати його для підтримки віддаленого завантаження файлів?
Сіямак Шахпасанд

1
ви мали намір подвоїти цитату "Content-Type: $ contentType";
Метт

set_time_limit (0); На мою думку, насправді не підходить. Можливо, більш розумна межа 24 години?
doublejr

Дякую, що перевірили мої помилки :)!
користувач1524615

15

Так. Підтримка міждіапазонів. Див. Розділ 14.35 RFC 2616 .

Це в основному означає, що ви повинні прочитати Rangeзаголовок і почати подавати файл із зазначеного зміщення.

Це означає, що ви не можете використовувати readfile (), оскільки він обслуговує весь файл. Замість цього спочатку використовуйте fopen () , потім fseek () у правильному положенні, а потім використовуйте fpassthru () для обслуговування файлу.


4
fpassthru - це не дуже гарна ідея, якщо файл має кілька мегабайт, можливо, у вас не вистачить пам'яті. Просто завантажте () і друкуйте () шматками.
Віллем

3
Тут чудово працює fpassthru із сотнями мегабайт. echo file_get_contents(...)не працював (ООМ). Тому я не думаю, що це питання. PHP 5.3.
Янус Троельсен

1
@JanusTroelsen Ні, це не так. Все залежить від конфігурації вашого сервера. Якщо у вас потужний сервер, на якому PHP виділяється багато пам'яті, то, можливо, це працює для вас чудово. У "слабких" конфігураціях (буквально: спільні хостинги) використання fpassthruне вдасться навіть у файлах на 50 МБ. Ви точно не повинні використовувати його, якщо ви обслуговуєте великі файли на слабкій конфігурації сервера. Як правильно вказує @Wimmer, fread+ printце все, що вам потрібно в цьому випадку.
трейдер

2
@trejder: Дивіться примітку до readfile () : readfile () не матиме жодних проблем із пам'яттю, навіть при надсиланні великих файлів, самостійно. Якщо ви зіткнулися з помилкою в пам'яті, переконайтесь, що буферизація вимкнена за допомогою ob_get_level ().
Янус Троельсен,

1
@trejder Проблема полягає в тому, що ви не налаштували правильне буферування виводу. Це відбувається автоматично, якщо ви скажете це на адресу: php.net/manual/en/…, наприклад, output_buffering = 4096 (і якщо ваша рамка цього не дозволяє, вам рамка смокче)
ZJR

11

По-справжньому приємний спосіб вирішити це без необхідності "катати свій власний" PHP-код - це використовувати модуль Apache mod_xsendfile. Потім у PHP ви просто встановите відповідні заголовки. Apache змушений робити свою справу.

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

2
Що робити, якщо ви хочете від’єднати файл після надсилання?
Janus Troelsen

1
Якщо ви хочете від’єднати файл після надсилання, вам потрібен спеціальний прапор для позначення цього, див. XSendFilePath <absolute path> [AllowFileDelete]( Tn123.org/mod_xsendfile/beta ).
Єнс А. Кох

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 тощо). Особливо, коли ви надсилаєте великі файли, це захистить повний запитуваний діапазон.
Пітер ван Гінкель

Коли це було додано до PHP? Завжди були там?
thomthom

1
Це Pecl, а не PHP. У мене немає цієї функції.
Гео

4

У верхній відповіді є різні помилки.

  1. Основна помилка: вона не обробляє заголовок діапазону правильно. 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');?
Мікко Ранталайнен

1
@MikkoRantalainen Я забув. Ви можете спробувати її видалити і подивитися, що станеться.
Mygod

1
На жаль, ви введете помилку у призначенні $ end у випадку, якщо $ match [2] не встановлено (наприклад, із запитом "Range = 0-"). Я використовував це замість цього:if(!isset($matches[2])) { $end=$fs-1; } else { $end = intval($matches[2]); }
Skynet

3

Так, ви можете використовувати для цього заголовок діапазону. Для повного завантаження вам потрібно надати ще 3 заголовки:

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

Тоді для перерваного завантаження вам потрібно перевірити заголовок діапазону запиту:

$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 та $ для змінних із заголовка запиту та скористайтесь fseek () для пошуку потрібного положення у файлі.


2
@ceejayoz: getallheaders () - це функція php, яку ви отримуєте, якщо використовуєте apache uk2.php.net/getallheaders
Том



1

Відновлення завантаження в HTTP здійснюється через Rangeзаголовок. Якщо запит містить Rangeзаголовок і якщо інші показники (наприклад If-Match, If-Unmodified-Since) вказують на те, що вміст не змінився з моменту початку завантаження, ви даєте код відповіді 206 (а не 200), вкажіть діапазон байтів, які ви повертаєте у Content-Rangeзаголовку, а потім вкажіть цей діапазон у тілі відповіді.

Я не знаю, як це зробити в PHP.


1

Дякую Тео! ваш метод не працював безпосередньо для потокової передачі divx, тому що я виявив, що програвач divx надсилає діапазони на зразок байтів = 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);
?>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.