Download ripristinabili quando si utilizza PHP per inviare il file?


104

Stiamo utilizzando uno script PHP per il tunneling dei download di file, poiché non vogliamo esporre il percorso assoluto del file scaricabile:

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

Purtroppo abbiamo notato che i download passati attraverso questo script non possono essere ripresi dall'utente finale.

Esiste un modo per supportare download ripristinabili con una tale soluzione basata su PHP?

Risposte:


102

La prima cosa che devi fare è inviare l' Accept-Ranges: bytesintestazione in tutte le risposte, per dire al cliente che supporti il ​​contenuto parziale. Poi, se la richiesta di una Range: bytes=x-y ricevuto intestazione (con xe ydi essere i numeri) si analizza la gamma il client richiede, apre il file come al solito, cercano xbyte avanti e inviare i successivi y- xbyte. Imposta anche la risposta su HTTP/1.0 206 Partial Content.

Senza aver testato nulla, questo potrebbe funzionare, più o meno:

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

Potrei aver perso qualcosa di ovvio e ho decisamente ignorato alcune potenziali fonti di errore, ma dovrebbe essere un inizio.

C'è una descrizione del contenuto parziale qui e ho trovato alcune informazioni sul contenuto parziale nella pagina della documentazione per fread .


3
Piccolo bug, la tua espressione regolare dovrebbe essere: preg_match ('/ bytes = (\ d +) - (\ d +)? /', $ _SERVER ['HTTP_RANGE'], $ corrisponde)
deepwell

1
Hai ragione e l'ho cambiato. Tuttavia, è comunque troppo semplicistico, secondo le specifiche puoi fare "bytes = xy", "bytes = -x", "bytes = x-", "bytes = xy, ab", ecc. Quindi il bug nel la versione precedente era la barra finale mancante, non la mancanza di un punto interrogativo.
Theo

7
Molto utile, ma ho dovuto apportare due piccole modifiche per farlo funzionare: 1. Se il client non invia l'endpoint nell'intervallo (poiché è implicito), $lengthsarà negativo. $length = (($matches[2]) ? intval($matches[2]) : $filesize) - $offset;lo risolve. 2. Content-Rangeconsidera il primo byte come byte 0, quindi l'ultimo byte è $filesize - 1. Pertanto, deve essere ($offset + $length - 1).
Dennis

1
Sopra non funziona per i download di grandi dimensioni, viene visualizzato un "Errore irreversibile PHP: dimensione della memoria consentita di XXXX byte esaurita (si è tentato di allocare XXX byte) in". Nel mio caso 100 MB erano troppo grandi. Fondamentalmente salvi tutto il file in una variabile e lo sputi.
sarah.ferguson

1
Puoi risolvere il problema dei file di grandi dimensioni leggendolo a pezzi invece che tutto in una volta.
dynamichael

71

EDIT 2017/01 - Ho scritto una libreria per farlo in PHP> = 7.0 https://github.com/DaveRandom/Resume

EDIT 2016/02 - Codice completamente riscritto in una serie di strumenti modulari e un utilizzo di esempio, piuttosto che una funzione monolitica. Le correzioni menzionate nei commenti di seguito sono state incorporate.


Una soluzione testata e funzionante (basata fortemente sulla risposta di Theo sopra) che si occupa di download ripristinabili, in una serie di pochi strumenti autonomi. Questo codice richiede PHP 5.4 o successivo.

Questa soluzione può ancora far fronte a un solo intervallo per richiesta, ma in qualsiasi circostanza con un browser standard a cui posso pensare, ciò non dovrebbe causare problemi.

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

Utilizzo di esempio:

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

Codice abbastanza carino qui. Ho trovato un bug sulla riga in cui è impostata $ length. Dovrebbe essere: $ length = $ end - $ start + 1;
bobwienholt

Come sospenderò il download
Prasanth Bendra

3
La lunghezza del contenuto deve essere impostata sulla dimensione effettiva del file o solo sul numero di byte parziali inviati? Questa pagina fa sembrare che dovrebbero essere i byte parziali, ma non è quello che viene fatto nel codice di esempio sopra. w3.org/Protocols/rfc2616/rfc2616-sec14.html
willus

3
Un altro piccolo errore di battitura: $start = $end - intval($range[0]);dovrebbe essererange[1]
BurninLeo

1
@ sarah.ferguson Codice completamente riscritto e aggiornato, vedi sopra.
DaveRandom

16

Funziona al 100% super controlla che lo sto usando e nessun problema più.

        /* 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
Ho votato positivamente perché il limite di velocità è davvero utile, tuttavia un controllo MD5 su un file ripreso (Firefox) ha mostrato una mancata corrispondenza. Str_replace per $ range è sbagliato, dovrebbe essere un'altra esplosione, il risultato reso numerico e un trattino aggiunto all'intestazione Content-Range.
WhoIsRich

Come personalizzarlo per supportare il download di file remoto?
Siyamak Shahpasand

1
volevi virgolette due volte 'Content-Type: $ contentType';
Matt

set_time_limit (0); non è proprio appropriato secondo me. Forse un limite più ragionevole di 24 ore?
volte il

Grazie per aver controllato i miei errori di battitura :)!
user1524615

15

Sì. Supporto per intervalli. Vedere la sezione RFC 2616 14.35 .

Fondamentalmente significa che dovresti leggere l' Rangeintestazione e iniziare a servire il file dall'offset specificato.

Ciò significa che non puoi usare readfile (), poiché serve l'intero file. Invece, usa prima fopen () , poi fseek () nella posizione corretta, e poi usa fpassthru () per servire il file.


4
fpassthru non è una buona idea se il file è di più megabyte, potresti esaurire la memoria. Basta fread () e print () in blocchi.
Willem

3
fpassthru funziona alla grande qui con centinaia di megabyte. echo file_get_contents(...)non ha funzionato (OOM). Quindi non penso che sia un problema. PHP 5.3.
Janus Troelsen

1
@JanusTroelsen No, non lo è. Tutto dipende dalla configurazione del tuo server. Se hai un server potente, con molta memoria assegnata a PHP, allora forse funziona bene per te. Su configurazioni "deboli" (letteralmente: hosting condiviso) l'utilizzo fpassthrufallirà anche su file da 50 MB. Non dovresti assolutamente usarlo, se stai servendo file di grandi dimensioni su una configurazione del server debole. Come sottolinea correttamente @Wimmer, fread+ printè tutto ciò di cui hai bisogno in questo caso.
trejder

2
@trejder: vedere la nota su readfile () : readfile () non presenterà alcun problema di memoria, anche quando si inviano file di grandi dimensioni, da solo. Se si verifica un errore di memoria insufficiente, assicurarsi che il buffering dell'output sia disattivato con ob_get_level ().
Janus Troelsen

1
@trejder il problema è che non hai configurato correttamente il buffering dell'output. Lo fa automaticamente la suddivisione in blocchi, se la dite: php.net/manual/en/... esempio output_buffering = 4096 (e se il vostro quadro non consente questo, si succhia quadro)
ZJR

11

Un modo davvero carino per risolvere questo problema senza dover eseguire il "rollio del proprio" codice PHP è utilizzare il modulo Apache mod_xsendfile. Quindi in PHP, devi solo impostare le intestazioni appropriate. Apache riesce a fare le sue cose.

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

2
Cosa succede se desideri scollegare il file dopo l'invio?
Janus Troelsen

1
Se si desidera scollegare il file dopo l'invio, è necessario un flag speciale per indicarlo, vedere XSendFilePath <absolute path> [AllowFileDelete]( tn123.org/mod_xsendfile/beta ).
Jens A. Koch

9

Se sei disposto a installare un nuovo modulo PECL, il modo più semplice per supportare download ripristinabili con PHP è attraverso http_send_file(), in questo modo

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

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

Lo usiamo per servire i contenuti archiviati nel database e funziona a meraviglia!


3
Funziona come un fascino. Tuttavia, fai attenzione a non avere il buffering dell'output (ob_start ecc.) Attivato. Soprattutto quando si inviano file di grandi dimensioni, questo bufferizzerà l'intero intervallo richiesto.
Pieter van Ginkel

Quando è stato aggiunto a PHP? Ci sei sempre stato?
thomthom

1
Questo è Pecl, non PHP. Non ho questa funzione.
Geo

4

La risposta principale ha vari bug.

  1. Il bug principale: non gestisce correttamente l'intestazione Range. bytes a-bdovrebbe significare [a, b]invece di [a, b)e bytes a-non viene gestito.
  2. Il bug minore: non usa il buffer per gestire l'output. Ciò potrebbe consumare troppa memoria e causare una bassa velocità per file di grandi dimensioni.

Ecco il mio codice modificato:

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

Perché questo bisogno ini_set('memory_limit', '-1');?
Mikko Rantalainen

1
@MikkoRantalainen mi sono dimenticato. Potresti provare a rimuoverlo e vedere cosa succede.
Mygod

1
Sfortunatamente si genererà un errore nell'assegnazione $ end nel caso in cui $ corrisponda [2] non è impostato (ad esempio con una richiesta "Range = 0-"). Ho usato questo invece:if(!isset($matches[2])) { $end=$fs-1; } else { $end = intval($matches[2]); }
Skynet

3

Sì, puoi utilizzare l'intestazione Range per questo. È necessario fornire altre 3 intestazioni al client per un download completo:

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

Quindi per un download interrotto è necessario controllare l'intestazione della richiesta Range da:

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

E in questo caso non dimenticare di servire il contenuto con il codice di stato 206:

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

Otterrai le variabili $ start e $ to dall'intestazione della richiesta e utilizzerai fseek () per cercare la posizione corretta nel file.


2
@ceejayoz: getallheaders () è una funzione php che ottieni se stai usando apache uk2.php.net/getallheaders
Tom Haigh



1

La ripresa dei download in HTTP avviene tramite l' Rangeintestazione. Se la richiesta contiene un Rangecolpo di testa, e se altri indicatori (ad esempio If-Match, If-Unmodified-Since) indicano che il contenuto non è cambiato da quando il download è stato avviato, si dà un codice 206 risposta (piuttosto che 200), indicare l'intervallo di byte stai tornando nelContent-Range intestazione, quindi fornire tale intervallo nel corpo risposta.

Tuttavia, non so come farlo in PHP.


1

Grazie Theo! il tuo metodo non ha funzionato direttamente per lo streaming divx perché ho scoperto che il lettore divx inviava intervalli come byte = 9932800-

ma mi ha mostrato come farlo quindi grazie: D

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

0

È possibile utilizzare il codice seguente per il supporto della richiesta di intervallo di byte su qualsiasi 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);
?>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.