Dosyayı göndermek için PHP kullanırken yeniden başlatılabilir indirmeler?


104

İndirilebilir dosyanın mutlak yolunu açığa çıkarmak istemediğimizden, dosya indirmelerini tünellemek için bir PHP komut dosyası kullanıyoruz:

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

Maalesef, bu komut dosyası üzerinden geçen indirmelerin son kullanıcı tarafından devam ettirilemeyeceğini fark ettik.

Bu tür PHP tabanlı bir çözümle devam ettirilebilir indirmeleri desteklemenin herhangi bir yolu var mı?

Yanıtlar:


102

Yapmanız gereken ilk şey Accept-Ranges: bytes, istemciye kısmi içeriği desteklediğinizi söylemek için tüm yanıtlarda başlığı göndermektir . Bir ile istek Ardından, Range: bytes=x-y başlığın (ile alınır xve yvarlık sayılar) Eğer istemci isteyen aralığı ayrıştırmak zamanki gibi dosyayı açın seek xbayt öncesinde ve sonraki göndermek y- xbayt. Ayrıca yanıtı olarak ayarlayın HTTP/1.0 206 Partial Content.

Hiçbir şey test edilmeden, bu aşağı yukarı işe yarayabilir:

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

Açık olan bir şeyi gözden kaçırmış olabilirim ve kesinlikle bazı potansiyel hata kaynaklarını görmezden geldim, ancak bu bir başlangıç ​​olmalı.

Burada kısmi içeriğin bir açıklaması var ve fread dokümantasyon sayfasında kısmi içerik hakkında bazı bilgiler buldum .


3
Küçük hata, normal ifadeniz şöyle olmalıdır: preg_match ('/ bytes = (\ d +) - (\ d +)? /', $ _SERVER ['HTTP_RANGE'], $ matches)
deepwell

1
Haklısın ve ben değiştirdim. Bununla birlikte, yine de çok basit, teknik özelliklere göre "bayt = xy", "bayt = -x", "bayt = x-", "bayt = xy, ab" vb. önceki sürüm, soru işaretinin olmaması değil, eksik eğik çizgiydi.
Theo

7
Çok yardımcı oldu, ancak çalışmasını sağlamak için iki küçük ayar yapmak zorunda kaldım: 1. İstemci aralıktaki bitiş noktasını göndermezse (örtük olduğu için) $lengthnegatif olacaktır. $length = (($matches[2]) ? intval($matches[2]) : $filesize) - $offset;bunu düzeltir. 2. Content-Rangeilk baytı bayt olarak ele alır 0, bu nedenle son bayt olur $filesize - 1. Bu nedenle olması gerekiyor ($offset + $length - 1).
Dennis

1
Yukarıdakiler büyük indirmeler için çalışmaz, "PHP Ölümcül hatası: XXXX baytlık izin verilen bellek boyutu tükendi (XXX bayt ayırmaya çalıştı)" mesajı alırsınız. Benim durumumda 100MB çok büyüktü. Temel olarak tüm dosyayı bir değişkene kaydedip tükürürsünüz.
sarah.ferguson

1
Büyük dosya sorununu tek seferde okumak yerine parçalar halinde okuyarak çözebilirsiniz.
dynamichael

71

EDIT 2017/01 - Bunu yapmak için PHP> = 7.0 https://github.com/DaveRandom/Resume'da bir kitaplık yazdım

EDIT 2016/02 - Kod, monolitik bir işlevden ziyade, örnek bir kullanım olan bir dizi modüler araca tamamen yeniden yazılmıştır. Aşağıdaki yorumlarda bahsedilen düzeltmeler dahil edilmiştir.


Birkaç bağımsız araçtan oluşan bir sette devam ettirilebilir indirmelerle ilgilenen test edilmiş, çalışan bir çözüm (ağırlıklı olarak Theo'nun yukarıdaki yanıtına dayanmaktadır). Bu kod, PHP 5.4 veya üstünü gerektirir.

Bu çözüm hala istek başına yalnızca bir aralıkla başa çıkabilir, ancak aklıma gelen standart bir tarayıcıyla herhangi bir koşulda bu bir soruna neden olmamalıdır.

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

Örnek kullanım:

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

Burada kod çok güzel. $ Length ayarının yapıldığı satırda bir hata buldum. $ Uzunluk = $ end - $ başlangıç ​​+ 1;
bobwienholt

İndirmeyi nasıl duraklatacağım
Prasanth Bendra

3
İçerik Uzunluğu gerçek dosya boyutuna mı yoksa sadece gönderilen kısmi bayt sayısına mı ayarlanmalıdır? Bu sayfa, kısmi baytlar olması gerektiği gibi görünmesini sağlar, ancak yukarıdaki örnek kodda yapılan bu değildir. w3.org/Protocols/rfc2616/rfc2616-sec14.html
2013

3
Başka bir küçük yazım hatası: $start = $end - intval($range[0]);olmalırange[1]
BurninLeo

1
@ sarah.ferguson Kod tamamen yeniden yazıldı ve güncellendi, yukarıya bakın.
DaveRandom

16

Bu% 100 süper çalışıyor, kullanıyorum ve artık sorun yok.

        /* 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
Hız sınırı gerçekten yararlı olduğu için oy verdim, ancak devam ettirilen bir dosyadaki (Firefox) MD5 kontrolü bir uyumsuzluk gösterdi. $ Aralığı için str_replace yanlış, başka bir patlama olmalı, sonuç sayısal olmalı ve Content-Range başlığına bir tire eklenmelidir.
WhoIsRich

Uzaktan dosya indirmeyi desteklemek için nasıl özelleştirilir?
Siyamak Shahpasand

1
'Content-Type: $ contentType'ı çift tırnak içine almak istediniz;
Matt

set_time_limit (0); bence pek uygun değil. 24 saatlik daha makul bir sınır belki?
05'te iki

Yazım hatalarımı kontrol ettiğiniz için teşekkür ederim :)!
user1524615

15

Evet. Düzenlemeleri destekleyin. RFC 2616 bölüm 14.35'e bakın .

Temel olarak, Rangebaşlığı okumanız ve dosyayı belirtilen ofsetten sunmaya başlamanız gerektiği anlamına gelir .

Bu, tüm dosyaya hizmet ettiği için readfile () kullanamayacağınız anlamına gelir. Bunun yerine, önce fopen () kullanın , sonra doğru konuma fseek () ve ardından dosyayı sunmak için fpassthru () kullanın.


4
fpassthru, dosya birden çok megabayt ise iyi bir fikir değildir, belleğiniz bitebilir. Sadece fread () ve parçalar halinde yazdır ().
Willem

3
fpassthru burada yüzlerce megabayt ile harika çalışıyor. echo file_get_contents(...)işe yaramadı (OOM). Bu yüzden bunun bir sorun olduğunu düşünmüyorum. PHP 5.3.
Janus Troelsen

1
@JanusTroelsen Hayır, onun değil. Hepsi sunucunuzun yapılandırmasına bağlıdır. PHP'ye ayrılmış çok fazla belleğe sahip güçlü bir sunucunuz varsa, o zaman belki sizin için iyi çalışıyor olabilir. "Zayıf" yapılandırmalarda (kelimenin tam anlamıyla: paylaşılan barındırmalar) kullanımı fpassthru50 MB'lık dosyalarda bile başarısız olacaktır. Zayıf sunucu yapılandırmasında büyük dosyalar sunuyorsanız kesinlikle kullanmamalısınız. @ Wimmer'ın doğru bir şekilde işaret ettiği gibi, bu durumda ihtiyacınız olan tek şey fread+ print.
trejder

2
@trejder: Readfile () ile ilgili nota bakın : readfile () kendi başına büyük dosyalar gönderirken bile herhangi bir bellek sorunu göstermez. Yetersiz bellek hatasıyla karşılaşırsanız, çıktı tamponlamasının ob_get_level () ile kapalı olduğundan emin olun.
Janus Troelsen

1
@trejder sorun, çıktı tamponlamanızı doğru şekilde yapılandırmadınız. Eğer söylerseniz, parçalamayı otomatik olarak yapar: php.net/manual/en/… örneğin output_buffering = 4096 (ve eğer çerçeveniz buna izin vermiyorsa, çerçeve berbattır)
ZJR

11

Bunu kendi PHP kodunuzu "yuvarlamak" zorunda kalmadan çözmenin gerçekten güzel bir yolu, mod_xsendfile Apache modülünü kullanmaktır. Ardından PHP'de uygun başlıkları ayarlarsınız. Apache kendi işini yapar.

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

2
Ya gönderdikten sonra dosyanın bağlantısını kaldırmak isterseniz?
Janus Troelsen

1
Gönderdikten sonra dosyanın bağlantısını kaldırmak istiyorsanız, bunu belirtmek için özel bir bayrağa ihtiyacınız var, bkz. XSendFilePath <absolute path> [AllowFileDelete]( Tn123.org/mod_xsendfile/beta ).
Jens A. Koch

9

Yeni bir PECL modülü takmak için istekli iseniz, PHP ile resumeable indirmeleri desteklemek için en kolay yolu geçer http_send_file()böyle,

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

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

Veritabanında depolanan içeriği sunmak için kullanıyoruz ve bir cazibe gibi çalışıyor!


3
Tıkır tıkır çalışıyor. Ancak, çıktı tamponlamasının (ob_start vb.) Açık olmamasına dikkat edin. Özellikle büyük dosyalar gönderirken, bu istenen tüm aralığı arabelleğe alacaktır.
Pieter van Ginkel

Bu PHP'ye ne zaman eklendi? Hep orada mısın?
thomthom

1
Bu Pecl, PHP değil. Bu işleve sahip değilim.
Geo

4

En iyi yanıtın çeşitli hataları var.

  1. En büyük hata: Range başlığını doğru şekilde işlemez. yerine ve ele alınmaz bytes a-banlamına gelmelidir .[a, b][a, b)bytes a-
  2. Küçük hata: Çıktıyı işlemek için tampon kullanmaz. Bu, çok fazla bellek tüketebilir ve büyük dosyalar için düşük hıza neden olabilir.

İşte değiştirilmiş kodum:

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

Buna neden ihtiyaç var ini_set('memory_limit', '-1');?
Mikko Rantalainen

1
@MikkoRantalainen unuttum. Kaldırmayı deneyebilir ve ne olacağını görebilirsiniz.
Mygod

1
Ne yazık ki, $ eşleşmeler [2] belirlenmemişse (örneğin "Aralık = 0-" isteği ile) $ end atamasında bir hata atacaksınız. Bunun yerine bunu kullandım:if(!isset($matches[2])) { $end=$fs-1; } else { $end = intval($matches[2]); }
Skynet

3

Evet, bunun için Range başlığını kullanabilirsiniz. Tam indirme için istemciye 3 başlık daha vermeniz gerekir:

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

Kesilen bir indirme için, Range istek başlığını şu şekilde kontrol etmeniz gerekir:

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

Ve bu durumda içeriği 206 durum kodu ile sunmayı unutmayın:

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

$ Start ve $ to değişkenlerini istek başlığından alacak ve dosyada doğru konumu aramak için fseek () kullanacaksınız.


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



1

HTTP'de indirmelerin devam ettirilmesi Rangebaşlık üzerinden yapılır . İstek bir Rangebaşlık içeriyorsa ve diğer göstergeler (örn If-Match. If-Unmodified-Since), İndirmenin başlatılmasından bu yana içeriğin değişmediğini gösteriyorsa, 206 yanıt kodu verirsiniz (200 yerine), döndürdüğünüz bayt aralığını belirtin içerisinde Content-Rangebaşlığın, daha sonra cevap gövdesinde bu aralığı sağlar.

Yine de bunu PHP'de nasıl yapacağımı bilmiyorum.


1

Theo teşekkürler! metodunuz doğrudan divx akışı için çalışmadı çünkü divx oynatıcısının bayt = 9932800- gibi aralıklar gönderdiğini buldum

ama bana bunu nasıl yapacağımı gösterdi çok teşekkürler: D

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

0

Herhangi bir tarayıcıda bayt aralığı isteği desteği için aşağıdaki kodu kullanabilirsiniz

    <?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);
?>
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.