ফাইলটি প্রেরণের জন্য পিএইচপি ব্যবহার করার সময় পুনরায় শুরুযোগ্য ডাউনলোডগুলি?


104

ফাইল ডাউনলোডগুলি টানেল করার জন্য আমরা একটি পিএইচপি স্ক্রিপ্টিং ব্যবহার করছি, যেহেতু আমরা ডাউনলোডযোগ্য ফাইলের পরম পথটি প্রকাশ করতে চাই না:

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

দুর্ভাগ্যক্রমে আমরা লক্ষ্য করেছি যে এই স্ক্রিপ্টটির মধ্য দিয়ে যাওয়া ডাউনলোডগুলি শেষ ব্যবহারকারীর দ্বারা পুনরায় শুরু করা যাবে না।

এই জাতীয় পিএইচপি-ভিত্তিক সমাধান সহ পুনঃসূচনাযোগ্য ডাউনলোডগুলি সমর্থন করার কোনও উপায় আছে কি?

উত্তর:


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

আমি সম্ভবত কিছু স্পষ্ট করে ফেলেছি, এবং আমি অবশ্যই ত্রুটির কিছু সম্ভাব্য উত্সকে এড়িয়ে গেছি, তবে এটি একটি শুরু হওয়া উচিত।

এখানে আংশিক সামগ্রীর বিবরণ রয়েছে এবং আমি ফ্রেডের জন্য ডকুমেন্টেশন পৃষ্ঠায় আংশিক সামগ্রীর কিছু তথ্য পেয়েছি ।


3
ছোট বাগ, আপনার নিয়মিত প্রকাশটি হওয়া উচিত: প্রিগ_ম্যাচ ('/ বাইটস = (\ ডি +)) - (\ ডি +)? /', $ _SERVER ['HTTP_RANGE'], $
ম্যাচস

1
আপনি ঠিক বলেছেন এবং আমি এটি পরিবর্তন করেছি। যাইহোক, আমি যাইহোক এটি খুব সরল, চশমা অনুসারে আপনি "বাইটস = এক্সআই", "বাইটস =-এক্স", "বাইটস = এক্স-", "বাইটস = এক্স, অ্যাব", ইত্যাদি করতে পারেন তাই বাগটিতে বাগ পূর্ববর্তী সংস্করণটি হ'ল শেষ স্ল্যাশ, প্রশ্ন চিহ্নের অভাব নয়।
থিও

7
খুব সহায়ক, তবে এটির কাজ করতে আমাকে দুটি ছোট ছোট টুইট করতে হয়েছিল: ১. ক্লায়েন্ট যদি পরিসীমাটিতে শেষ পয়েন্টটি না পাঠায় (যেহেতু এটি অন্তর্ভুক্ত) $lengthতবে এটি নেতিবাচক হবে। $length = (($matches[2]) ? intval($matches[2]) : $filesize) - $offset;এটি স্থির করে ২. Content-Rangeপ্রথম বাইটকে বাইট হিসাবে গণ্য করে 0, তাই শেষ বাইটটি $filesize - 1। সুতরাং, এটি হতে হবে ($offset + $length - 1)
ডেনিস

1
উপরে বড় ডাউনলোডগুলির জন্য কাজ করে না, আপনি একটি "পিএইচপি মারাত্মক ত্রুটি: XXXX বাইটের অনুমোদিত মেমরির আকার অবসন্ন (এক্সএক্সএক্স বাইট বরাদ্দ করার চেষ্টা করা হয়েছে)" তে পেয়েছেন। আমার ক্ষেত্রে 100MB খুব বড় ছিল। আপনি মূলত সমস্ত ফাইল একটি ভেরিয়েবলে সংরক্ষণ করুন এবং এটি থুথু আউট করে।
sarah.ferguson

1
আপনি বৃহত্তর ফাইল সমস্যাটি একবারে না করে বারে বারে পড়ে সমাধান করতে পারেন।
ডায়নামিকেল

71

সম্পাদনা 2017/01 - PHP> = 7.0 https://github.com/DaveRandom/Resume এ করার জন্য আমি একটি গ্রন্থাগার লিখেছিলাম

সম্পাদনা 2016/02 - কোড একরকম ফাংশন না করে মডুলার সরঞ্জামগুলির একটি সেটটিতে সম্পূর্ণরূপে পুনরায় লিখিত an নীচে মন্তব্যে উল্লিখিত সংশোধনগুলি অন্তর্ভুক্ত করা হয়েছে।


একটি পরীক্ষিত, কার্যনির্বাহী সমাধান (উপরে থিওর উত্তরের উপর ভিত্তি করে) যা কয়েকটি স্ট্যান্ডেলোন সরঞ্জামের সেটগুলিতে পুনরায় শুরুযোগ্য ডাউনলোডগুলির সাথে সম্পর্কিত। এই কোডটির জন্য পিএইচপি 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;

খুব সুন্দর এখানে কোড। লাইনটিতে যেখানে দৈর্ঘ্য নির্ধারণ করা হয়েছে সেখানে আমি একটি বাগ পেয়েছি। হওয়া উচিত: $ দৈর্ঘ্য = $ শেষ - $ শুরু + 1;
ববউইনহোল্ট

আমি ডাউনলোডে কীভাবে বিরতি দেব
প্রশান্ত বেন্দ্র

3
সামগ্রী-দৈর্ঘ্যটি কি আসল ফাইল আকারে সেট করা উচিত, বা কেবলমাত্র আংশিক বাইটের সংখ্যা পাঠানো হচ্ছে? এই পৃষ্ঠাটি এটি দেখতে আংশিক বাইট হওয়া উচিত বলে মনে করে, তবে উপরের উদাহরণ কোডে এটি করা হয়নি। w3.org/Protocols/rfc2616/rfc2616-sec14.html
উইলাস

3
আরেকটি ছোট টাইপো: $start = $end - intval($range[0]);হওয়া উচিতrange[1]
বার্নিনলিয়ো

1
@ sarah.ferguson কোড সম্পূর্ণ নতুন করে লেখা এবং আপডেট হয়েছে, উপরে দেখুন।
ডেভর্যান্ডম

16

এটি আমি এটি ব্যবহার করছি এবং এটির আর কোনও সমস্যা নেই এটি 100% সুপার এটি পরীক্ষা করে works

        /* 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
আমি উচ্চারণ করেছি কারণ গতির সীমাটি সত্যই কার্যকর, তবে পুনরায় চালু হওয়া ফাইলের (এম ফায়ারফক্স) এমডি 5 চেক একটি অমিল দেখায়। $ রেঞ্জের জন্য str_replace ভুল, অন্য বিস্ফোরণ হওয়া উচিত, ফলাফলটি সংখ্যাসূচক হয় এবং কন্টেন্ট-রেঞ্জের শিরোনামে একটি ড্যাশ যুক্ত হয়।
WHIsRich

সমর্থন রিমোট ফাইল ডাউনলোডের জন্য কীভাবে এটি কাস্টমাইজ করবেন?
সিয়ামাক শাহপাশান্দ

1
আপনি বোঝাতে চেয়েছিলেন 'বিষয়বস্তুর ধরণ: T কন্টেন্ট টাইপ';
ম্যাট

set_time_limit (0); আমার মতে সত্যিই উপযুক্ত নয়। 24 ঘন্টা আরও যুক্তিসঙ্গত সীমা হতে পারে?
twojr

আমার টাইপস যাচাই করার জন্য আপনাকে ধন্যবাদ :)!
ব্যবহারকারী 1524615

15

হ্যাঁ. সমর্থন byteranges। দেখুন বোঝায় যা RFC 2616 অধ্যায় 14,35

এটির মূলত অর্থ হ'ল আপনার Rangeশিরোনামটি পড়া উচিত এবং নির্দিষ্ট অফসেট থেকে ফাইলটি সরবরাহ করা শুরু করা উচিত ।

এর অর্থ হ'ল আপনি পঠন ফাইল () ব্যবহার করতে পারবেন না, যেহেতু এটি পুরো ফাইলটিকে পরিবেশন করে। পরিবর্তে, প্রথমে fopen () ব্যবহার করুন , তারপরে fseek () সঠিক অবস্থানে এবং তারপরে ফাইলটি পরিবেশন করতে fpassthru () ব্যবহার করুন।


4
যদি ফাইলটি একাধিক মেগাবাইট হয়, তবে আপনার স্মৃতিশক্তি শেষ হয়ে যাবে f খণ্ডগুলিতে কেবল ফ্রেড () এবং মুদ্রণ ()।
উইলেম

3
শত শত মেগাবাইটের সাথে এখানে এফপাসস্ট্রু দুর্দান্ত কাজ করে। echo file_get_contents(...)(OOM) কাজ করেনি। সুতরাং আমি মনে করি না যে এটি একটি সমস্যা। পিএইচপি 5.3।
জানুস ট্রয়লসন

1
পছন্দ করেছেন এটি সব আপনার সার্ভারের কনফিগারেশনের উপর নির্ভর করে। আপনার যদি একটি শক্তিশালী সার্ভার থাকে, পিএইচপি-র জন্য প্রচুর মেমরি বরাদ্দ করা হয়, তবে এটি আপনার পক্ষে ঠিক কাজ করে। "দুর্বল" কনফিগারেশনগুলিতে (আক্ষরিক: ভাগ করে নেওয়া হোস্টিং) fpassthruএমনকি 50 এমবি ফাইলগুলিতে ব্যর্থ হবে। আপনি দুর্বল সার্ভার কনফিগারেশনে বড় ফাইলগুলি সরবরাহ করে থাকলে অবশ্যই এটি ব্যবহার করা উচিত নয়। @Wimmer সঠিকভাবে দেখায় যে fread+ + printআপনি এই ক্ষেত্রে প্রয়োজন সব হয়।
ট্রেজার্ড

2
@ ট্রেজেদার: রিডফাইলে নোটটি দেখুন () : রিডফিল () বড় আকারের ফাইলগুলি পাঠানোর সময়, নিজে থেকে কোনও মেমরির সমস্যা উপস্থাপন করবে না। আপনি যদি মেমরির একটি ত্রুটির মুখোমুখি হন তবে নিশ্চিত হয়ে নিন যে আউটপুট বাফারিং ob_get_level () দিয়ে বন্ধ রয়েছে।
জানুস ট্রয়লসেন

1
@ ট্র্যাজেদার সমস্যাটি হ'ল আপনি নিজের আউটপুট বাফারিংটি সঠিকভাবে কনফিগার করেননি। এটি স্বয়ংক্রিয়ভাবে ছিন্নমূল হয়ে যায়, যদি আপনি এটিকে বলতে পারেন: php.net/manual/en/… যেমন আউটপুট_বফারিং = 4096 (এবং যদি আপনার কাঠামো এটির অনুমতি দেয় না তবে আপনার কাঠামো সফল হয়)
জেডজেআর

11

পিএইচপি কোডটি "নিজের নিজের রোল" না করেই এটিকে সমাধান করার একটি দুর্দান্ত উপায় হ'ল Mod_xsendfile অ্যাপাচি মডিউলটি ব্যবহার করা। তারপরে পিএইচপি-তে, আপনি কেবল উপযুক্ত শিরোনাম সেট করলেন set আপাচে তার জিনিস করতে হবে।

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

2
আপনি যদি পাঠানোর পরে ফাইলটি লিঙ্কমুক্ত করতে চান?
জানুস ট্রয়লসেন

1
প্রেরণের পরেও যদি আপনি ফাইলটি লিঙ্কমুক্ত করতে চান তবে এটি নির্দেশ করতে আপনাকে একটি বিশেষ পতাকা প্রয়োজন, দেখুন XSendFilePath <absolute path> [AllowFileDelete]( tn123.org/mod_xsendfile/beta )।
জেনস এ। কোচ

9

যদি আপনি একটি নতুন PECL মডিউল ইনস্টল করতে ইচ্ছুক হন, তাহলে সবচেয়ে সহজ উপায় পিএইচপি সঙ্গে resumeable ডাউনলোডসমূহ সমর্থন করার জন্য মাধ্যমে হয় 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 ইত্যাদি) চালু নেই। বিশেষত বড় ফাইলগুলি প্রেরণের সময়, এটি সম্পূর্ণ অনুরোধ করা পরিসীমা বাফার করবে।
পিটার ভ্যান জিনকেল

কখন এই পিএইচপি যুক্ত হয়েছিল? সবসময় আছে?
থমথম

1
এটি পেকল, পিএইচপি নয়। আমার এই ফাংশনটি নেই
জিও

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
পুনঃটুইট করেছেন আপনি এটিকে সরাতে চেষ্টা করতে পারেন এবং দেখুন কী হয়।
মাইগড

1
দুর্ভাগ্যক্রমে আপনি $ ম্যাচ [2] সেট না হওয়ার ক্ষেত্রে $ শেষ অ্যাসাইনমেন্টে একটি ত্রুটি ফেলবেন (উদাহরণস্বরূপ "রেঞ্জ = 0-" অনুরোধ সহ)। পরিবর্তে আমি এটি ব্যবহার করেছি:if(!isset($matches[2])) { $end=$fs-1; } else { $end = intval($matches[2]); }
স্কাইনেট

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 . ";");

আপনি অনুরোধ শিরোনাম থেকে ভেরিয়েবলের জন্য $ শুরু এবং $ পাবেন এবং ফাইলেক () ব্যবহার করে ফাইলে সঠিক অবস্থানটি পাবেন।


2
@ceejayoz: getallheaders () একটি পিএইচপি ফাংশন যা আপনি অ্যাপাচি uk2.php.net/getallheaders
টম হাই


2

ছোট রচয়িতা সক্ষম ক্লাস যা pecl http_send_file এর মতো কাজ করে। এর অর্থ পুনঃসূচনাযোগ্য ডাউনলোডগুলি এবং থ্রোটলের জন্য সমর্থন। https://github.com/diversen/http-send-file


1

এইচটিটিপিতে ডাউনলোডগুলি পুনরায় শুরু করা Rangeশিরোনামের মাধ্যমে করা হয় । অনুরোধে কোনো থাকে Rangeহেডার, এবং অন্যান্য সূচক (যেমন যদি If-Match, If-Unmodified-Since), ইঙ্গিত করে যে বিষয়বস্তু যেহেতু ডাউনলোড শুরু হয়েছিল, আপনি একটি 206 প্রতিক্রিয়া কোড (বরং 200 বেশি) দিতে হবে না পরিবর্তিত হয়েছে পরিসীমা ইঙ্গিত আপনি ফিরে করছি বাইট মধ্যেContent-Range হেডার, তারপর প্রতিক্রিয়া শরীরে যে পরিসীমা প্রদান।

যদিও পিএইচপি তে এটি করতে হয় তা আমি জানি না।


1

থিও ধন্যবাদ! আপনার পদ্ধতিটি সরাসরি ডিভিক্সের স্ট্রিমিংয়ের জন্য কাজ করেনি কারণ আমি পেয়েছি যে ডিভিক্স প্লেয়ার বাইটস = 9932800- এর মতো রেঞ্জগুলি প্রেরণ করছে found

তবে এটি আমাকে কীভাবে এটি করতে হবে তা ধন্যবাদ জানিয়েছিল: ডি

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

0

আপনি যে কোনও ব্রাউজার জুড়ে বাইট রেঞ্জ অনুরোধ সমর্থন করার জন্য নীচের কোডটি ব্যবহার করতে পারেন

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

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

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

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

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

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

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

fclose($file);

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

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

// don't forget to send the data too
print($data);
?>
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.