Download dilanjutkan saat menggunakan PHP untuk mengirim file?

104

Kami menggunakan skrip PHP untuk mengunduh file tunneling, karena kami tidak ingin mengekspos jalur absolut dari file yang dapat diunduh:

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

Sayangnya, kami melihat bahwa unduhan yang melewati skrip ini tidak dapat dilanjutkan oleh pengguna akhir.

Apakah ada cara untuk mendukung unduhan yang dapat dilanjutkan dengan solusi berbasis PHP seperti itu?

Mark Amery
sumber

Jawaban:

102

Hal pertama yang perlu Anda lakukan adalah mengirim Accept-Ranges: bytesheader di semua tanggapan, untuk memberi tahu klien bahwa Anda mendukung konten parsial. Kemudian, jika permintaan dengan Range: bytes=x-y header diterima (dengan xdan ymenjadi nomor) Anda mengurai berbagai klien meminta, buka file seperti biasa, seek xbyte depan dan mengirim berikutnya y- xbyte. Juga setel tanggapannya ke HTTP/1.0 206 Partial Content.

Tanpa menguji apa pun, ini bisa berhasil, kurang lebih:

$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);

Saya mungkin telah melewatkan sesuatu yang jelas, dan saya pasti telah mengabaikan beberapa potensi sumber kesalahan, tetapi ini seharusnya menjadi permulaan.

Ada deskripsi konten parsial di sini dan saya menemukan beberapa info tentang konten parsial di halaman dokumentasi untuk fread .

Theo
sumber
3
Bug kecil, ekspresi reguler Anda harus: preg_match ('/ bytes = (\ d +) - (\ d +)? /', $ _SERVER ['HTTP_RANGE'], $ kecocokan)
deepwell
1
Anda benar dan saya telah mengubahnya. Namun, saya itu terlalu sederhana pula, menurut spesifikasi Anda dapat melakukan "bytes = xy", "bytes = -x", "bytes = x-", "bytes = xy, ab", dll. Jadi bug di versi sebelumnya adalah garis miring akhir yang hilang, bukan kurangnya tanda tanya.
Theo
7
Sangat membantu, tetapi saya harus membuat dua perubahan kecil untuk membuatnya bekerja: 1. Jika klien tidak mengirim titik akhir dalam kisaran (karena itu implisit), $lengthakan menjadi negatif. $length = (($matches[2]) ? intval($matches[2]) : $filesize) - $offset;memperbaikinya. 2. Content-Rangememperlakukan byte pertama sebagai byte 0, jadi byte terakhir adalah $filesize - 1. Oleh karena itu, harus demikian ($offset + $length - 1).
Dennis
1
Di atas tidak berfungsi untuk download besar, Anda mendapatkan pesan "PHP Fatal error: Ukuran memori yang diizinkan sebesar XXXX byte habis (mencoba mengalokasikan XXX byte) dalam". Dalam kasus saya, 100MB terlalu besar. Anda pada dasarnya menyimpan semua file dalam sebuah variabel dan memuntahkannya.
sarah.ferguson
1
Anda dapat menyelesaikan masalah file besar dengan membacanya dalam potongan-potongan alih-alih sekaligus.
dynamichael
71

EDIT 2017/01 - Saya menulis perpustakaan untuk melakukan ini di PHP> = 7.0 https://github.com/DaveRandom/Resume

EDIT 2016/02 - Kode sepenuhnya ditulis ulang ke satu set alat modular, sebuah contoh penggunaan, bukan fungsi monolitik. Koreksi yang disebutkan dalam komentar di bawah telah dimasukkan.


Solusi yang teruji dan berfungsi (berdasarkan jawaban Theo di atas) yang berhubungan dengan unduhan yang dapat dilanjutkan, dalam satu set beberapa alat mandiri. Kode ini membutuhkan PHP 5.4 atau yang lebih baru.

Solusi ini masih hanya dapat menangani satu rentang per permintaan, tetapi dalam keadaan apa pun dengan browser standar yang dapat saya pikirkan, ini seharusnya tidak menimbulkan masalah.

<?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);
    }
}

Contoh penggunaan:

<?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;
DaveRandom
sumber
Kode yang cukup bagus di sini. Saya menemukan bug di baris di mana $ length disetel. Seharusnya: $ length = $ end - $ start + 1;
bobwienholt
Bagaimana saya akan menjeda pengunduhan
Prasanth Bendra
3
Haruskah Panjang Konten disetel ke ukuran file sebenarnya, atau hanya jumlah sebagian byte yang dikirim? Halaman ini membuatnya tampak seperti byte parsial, tetapi bukan itu yang dilakukan dalam kode contoh di atas. w3.org/Protocols/rfc2616/rfc2616-sec14.html
willus
3
Salah ketik kecil lainnya: $start = $end - intval($range[0]);harusrange[1]
BurninLeo
1
@ sarah.ferguson Kode sepenuhnya ditulis ulang dan diperbarui, lihat di atas.
DaveRandom
16

Ini berfungsi 100% super, periksa Saya menggunakannya dan tidak ada masalah lagi.

        /* 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);
pengguna1524615
sumber
1
Saya memberi suara positif karena batas kecepatan sangat berguna, namun pemeriksaan MD5 pada file yang dilanjutkan (Firefox) menunjukkan ketidakcocokan. Str_replace untuk $ range salah, harus meledak lain, hasilnya dibuat numerik, dan tanda hubung ditambahkan ke header Content-Range.
WhoIsRich
Bagaimana cara menyesuaikannya untuk mendukung pengunduhan file jarak jauh?
Siyamak Shahpasand
1
Anda bermaksud untuk mengutip ganda 'Jenis-Konten: $ contentType';
Matt
set_time_limit (0); menurut saya kurang tepat. Mungkin batas 24 jam yang lebih masuk akal?
kalijr
Terima kasih telah memeriksa kesalahan ketik saya :)!
pengguna1524615
15

Iya. Mendukung byteranges. Lihat RFC 2616 bagian 14.35 .

Ini pada dasarnya berarti bahwa Anda harus membaca Range header, dan mulai menyajikan file dari offset yang ditentukan.

Ini berarti Anda tidak dapat menggunakan readfile (), karena itu melayani seluruh file. Sebagai gantinya, gunakan fopen () terlebih dahulu, lalu fseek () ke posisi yang benar, lalu gunakan fpassthru () untuk menyajikan file.

Sietse
sumber
4
fpassthru bukanlah ide yang baik jika file berukuran beberapa megabyte, Anda mungkin kehabisan memori. Hanya fread () dan print () dalam potongan.
Willem
3
fpassthru berfungsi dengan baik di sini dengan ratusan megabyte. echo file_get_contents(...)tidak bekerja (OOM). Jadi menurut saya itu bukan masalah. PHP 5.3.
Janus Troelsen
1
@JanusTroelsen Tidak, tidak. Itu semua tergantung pada konfigurasi server Anda. Jika Anda memiliki server yang kuat, dengan banyak memori yang dialokasikan untuk PHP, mungkin itu berfungsi dengan baik untuk Anda. Pada konfigurasi "lemah" (secara harfiah: host bersama) penggunaan fpassthruakan gagal bahkan pada file 50 MB. Anda sebaiknya tidak menggunakannya, jika Anda menyajikan file besar pada konfigurasi server yang lemah. Seperti yang @Wimmer tunjukkan dengan benar, fread+ printadalah segalanya, yang Anda butuhkan dalam kasus ini.
trejder
2
@trejder: Lihat catatan di readfile () : readfile () tidak akan menghadirkan masalah memori apa pun, bahkan saat mengirim file besar, dengan sendirinya. Jika Anda mengalami kesalahan kehabisan memori, pastikan buffering keluaran dimatikan dengan ob_get_level ().
Janus Troelsen
1
@ trejder masalahnya adalah Anda tidak mengkonfigurasi buffering keluaran dengan benar. Itu melakukan chunking secara otomatis, jika Anda memberitahukannya ke: php.net/manual/en/… misalnya output_buffering = 4096 (dan jika framework Anda tidak mengizinkan ini, framework Anda payah)
ZJR
11

Cara yang sangat bagus untuk mengatasi ini tanpa harus "menggulung kode PHP Anda sendiri" adalah dengan menggunakan modul Apache mod_xsendfile. Kemudian di PHP, Anda tinggal mengatur header yang sesuai. Apache bisa melakukan tugasnya.

header("X-Sendfile: /path/to/file");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; file=\"filename\"");
Jonathan Hawkes
sumber
2
Bagaimana jika Anda ingin membatalkan tautan file setelah mengirim?
Janus Troelsen
1
Jika Anda ingin membatalkan tautan file setelah mengirim Anda memerlukan flag khusus untuk menunjukkannya, lihat XSendFilePath <absolute path> [AllowFileDelete]( tn123.org/mod_xsendfile/beta ).
Jens A. Koch
9

Jika Anda ingin memasang modul PECL baru, cara termudah untuk mendukung unduhan yang dapat dilanjutkan dengan PHP adalah melalui http_send_file(), seperti ini

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

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

Kami menggunakannya untuk menyajikan konten yang disimpan database dan berfungsi dengan sangat baik!

Justin T.
sumber
3
Bekerja seperti pesona. Namun berhati-hatilah karena Anda tidak mengaktifkan buffering keluaran (ob_start dll). Terutama saat mengirim file besar, ini akan menyangga rentang lengkap yang diminta.
Pieter van Ginkel
Kapan ini ditambahkan ke PHP? Selalu disana?
thomthom
1
Itu Pecl, bukan PHP. Saya tidak memiliki fungsi ini.
Geo
4

Jawaban teratas memiliki berbagai bug.

  1. Bug utama: Ini tidak menangani header Range dengan benar. bytes a-bseharusnya berarti [a, b]bukan [a, b), dan bytes a-tidak ditangani.
  2. Bug minor: Tidak menggunakan buffer untuk menangani keluaran. Ini dapat menghabiskan terlalu banyak memori dan menyebabkan kecepatan rendah untuk file besar.

Ini kode saya yang dimodifikasi:

// 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);
Tuhanku
sumber
Mengapa ini perlu ini_set('memory_limit', '-1');?
Mikko Rantalainen
1
@IkkoRantalainen Saya lupa. Anda dapat mencoba menghapusnya dan melihat apa yang terjadi.
Mygod
1
Sayangnya Anda akan melempar kesalahan dalam $ end assignment jika $ pertandingan [2] tidak disetel (misalnya dengan permintaan "Range = 0-"). Saya menggunakan ini sebagai gantinya:if(!isset($matches[2])) { $end=$fs-1; } else { $end = intval($matches[2]); }
Skynet
3

Ya, Anda dapat menggunakan header Range untuk itu. Anda perlu memberikan 3 tajuk lagi kepada klien untuk unduhan lengkap:

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

Dari pada unduhan yang terputus, Anda perlu memeriksa header permintaan Range dengan:

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

Dan dalam hal ini jangan lupa untuk menyajikan konten dengan 206 kode status:

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

Anda akan mendapatkan $ start dan $ to variabel dari header permintaan, dan menggunakan fseek () untuk mencari posisi yang benar dalam file.

Zsolt Szeberenyi
sumber
2
@ceejayoz: getallheaders () adalah fungsi php yang Anda dapatkan jika Anda menggunakan apache uk2.php.net/getallheaders
Tom Haigh
2

Kelas berkemampuan komposer kecil yang bekerja dengan cara yang sama seperti pecl http_send_file. Ini berarti dukungan untuk unduhan dan throttle yang dapat dilanjutkan. https://github.com/diversen/http-send-file

dennis
sumber
1

Melanjutkan unduhan dalam HTTP dilakukan melalui Rangeheader. Jika permintaan berisi Rangeheader, dan jika indikator lain (misalnya If-Match, If-Unmodified-Since) menunjukkan bahwa konten belum berubah sejak pengunduhan dimulai, Anda memberikan 206 kode tanggapan (bukan 200), tunjukkan rentang byte yang Anda kembalikan dalamContent-Range header, lalu berikan rentang tersebut di isi respons.

Saya tidak tahu bagaimana melakukan itu di PHP.

Mike Dimmick
sumber
1

Terima kasih Theo! metode Anda tidak langsung berfungsi untuk streaming divx karena saya menemukan pemutar divx mengirim rentang seperti byte = 9932800-

tapi itu menunjukkan kepada saya bagaimana melakukannya jadi terima kasih: D

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

Anda dapat menggunakan kode di bawah ini untuk dukungan permintaan rentang byte di semua browser

    <?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);
?>
smurf
sumber