วิธีที่เร็วที่สุดในการแสดงไฟล์โดยใช้ PHP


99

ฉันกำลังพยายามรวบรวมฟังก์ชันที่รับเส้นทางไฟล์ระบุว่ามันคืออะไรตั้งค่าส่วนหัวที่เหมาะสมและทำหน้าที่เหมือนกับที่ Apache ทำ

เหตุผลที่ฉันทำเช่นนี้เป็นเพราะฉันต้องใช้ PHP เพื่อประมวลผลข้อมูลบางอย่างเกี่ยวกับคำขอก่อนที่จะให้บริการไฟล์

ความเร็วเป็นสิ่งสำคัญ

virtual () ไม่ใช่ตัวเลือก

ต้องทำงานในสภาพแวดล้อมการโฮสต์ที่ใช้ร่วมกันโดยที่ผู้ใช้ไม่สามารถควบคุมเว็บเซิร์ฟเวอร์ได้ (Apache / nginx ฯลฯ )

นี่คือสิ่งที่ฉันมีจนถึงตอนนี้:

File::output($path);

<?php
class File {
static function output($path) {
    // Check if the file exists
    if(!File::exists($path)) {
        header('HTTP/1.0 404 Not Found');
        exit();
    }

    // Set the content-type header
    header('Content-Type: '.File::mimeType($path));

    // Handle caching
    $fileModificationTime = gmdate('D, d M Y H:i:s', File::modificationTime($path)).' GMT';
    $headers = getallheaders();
    if(isset($headers['If-Modified-Since']) && $headers['If-Modified-Since'] == $fileModificationTime) {
        header('HTTP/1.1 304 Not Modified');
        exit();
    }
    header('Last-Modified: '.$fileModificationTime);

    // Read the file
    readfile($path);

    exit();
}

static function mimeType($path) {
    preg_match("|\.([a-z0-9]{2,4})$|i", $path, $fileSuffix);

    switch(strtolower($fileSuffix[1])) {
        case 'js' :
            return 'application/x-javascript';
        case 'json' :
            return 'application/json';
        case 'jpg' :
        case 'jpeg' :
        case 'jpe' :
            return 'image/jpg';
        case 'png' :
        case 'gif' :
        case 'bmp' :
        case 'tiff' :
            return 'image/'.strtolower($fileSuffix[1]);
        case 'css' :
            return 'text/css';
        case 'xml' :
            return 'application/xml';
        case 'doc' :
        case 'docx' :
            return 'application/msword';
        case 'xls' :
        case 'xlt' :
        case 'xlm' :
        case 'xld' :
        case 'xla' :
        case 'xlc' :
        case 'xlw' :
        case 'xll' :
            return 'application/vnd.ms-excel';
        case 'ppt' :
        case 'pps' :
            return 'application/vnd.ms-powerpoint';
        case 'rtf' :
            return 'application/rtf';
        case 'pdf' :
            return 'application/pdf';
        case 'html' :
        case 'htm' :
        case 'php' :
            return 'text/html';
        case 'txt' :
            return 'text/plain';
        case 'mpeg' :
        case 'mpg' :
        case 'mpe' :
            return 'video/mpeg';
        case 'mp3' :
            return 'audio/mpeg3';
        case 'wav' :
            return 'audio/wav';
        case 'aiff' :
        case 'aif' :
            return 'audio/aiff';
        case 'avi' :
            return 'video/msvideo';
        case 'wmv' :
            return 'video/x-ms-wmv';
        case 'mov' :
            return 'video/quicktime';
        case 'zip' :
            return 'application/zip';
        case 'tar' :
            return 'application/x-tar';
        case 'swf' :
            return 'application/x-shockwave-flash';
        default :
            if(function_exists('mime_content_type')) {
                $fileSuffix = mime_content_type($path);
            }
            return 'unknown/' . trim($fileSuffix[0], '.');
    }
}
}
?>

11
ทำไมคุณไม่ปล่อยให้ Apache ทำเช่นนี้? มันจะเร็วกว่าการเริ่มต้นล่าม PHP อย่างมาก ...
Billy ONeal

4
ฉันต้องดำเนินการตามคำขอและจัดเก็บข้อมูลบางอย่างในฐานข้อมูลก่อนที่จะส่งออกไฟล์
Kirk Ouimet

3
ฉันขอแนะนำวิธีที่จะได้รับการขยายโดยไม่ต้องแสดงออกปกติราคาแพงมากขึ้น: $extension = end(explode(".", $pathToFile))หรือคุณสามารถทำมันได้ด้วย substr และ $extension = substr($pathToFile, strrpos($pathToFile, '.'))strrpos: นอกจากนี้mime_content_type()คุณสามารถลองใช้ระบบโทร:$mimetype = exec("file -bi '$pathToFile'", $output);
Fanis Hatzidakis

คุณหมายถึงอะไรเร็วที่สุด ? เวลาดาวน์โหลดเร็วที่สุด?
Alix Axel

คำตอบ:


140

คำตอบก่อนหน้าของฉันเป็นเพียงบางส่วนและไม่ได้รับการจัดทำเป็นเอกสารอย่างดีนี่คือการอัปเดตพร้อมสรุปวิธีแก้ปัญหาจากคำตอบและจากผู้อื่นในการสนทนา

โซลูชันจะเรียงลำดับจากโซลูชันที่ดีที่สุดไปจนถึงแย่ที่สุด แต่ยังมาจากโซลูชันที่ต้องการการควบคุมมากที่สุดบนเว็บเซิร์ฟเวอร์ไปจนถึงโซลูชันที่ต้องการน้อยกว่า ดูเหมือนจะไม่มีวิธีง่ายๆที่จะมีวิธีแก้ปัญหาเดียวที่ทั้งรวดเร็วและใช้ได้ทุกที่


ใช้ส่วนหัว X-SendFile

ตามที่ผู้อื่นบันทึกไว้เป็นวิธีที่ดีที่สุด พื้นฐานคือคุณทำการควบคุมการเข้าถึงของคุณใน php แล้วแทนที่จะส่งไฟล์ด้วยตัวคุณเองคุณบอกให้เว็บเซิร์ฟเวอร์ทำ

รหัส php พื้นฐานคือ:

header("X-Sendfile: $file_name");
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($file_name) . '"');

$file_nameพา ธ แบบเต็มบนระบบไฟล์อยู่ที่ไหน

ปัญหาหลักในการแก้ปัญหานี้คือต้องได้รับอนุญาตจากเว็บเซิร์ฟเวอร์และไม่ได้ติดตั้งโดยค่าเริ่มต้น (apache) ไม่ทำงานตามค่าเริ่มต้น (lighttpd) หรือต้องการการกำหนดค่าเฉพาะ (nginx)

Apache

ภายใต้ apache หากคุณใช้ mod_php คุณต้องติดตั้งโมดูลที่เรียกว่าmod_xsendfileจากนั้นกำหนดค่า (ใน apache config หรือ. htaccess หากคุณอนุญาต)

XSendFile on
XSendFilePath /home/www/example.com/htdocs/files/

XSendFilePathด้วยโมดูลนี้เส้นทางของไฟล์อย่างใดอย่างหนึ่งอาจจะแน่นอนหรือเทียบกับที่ระบุไว้

Lighttpd

mod_fastcgi รองรับสิ่งนี้เมื่อกำหนดค่าด้วย

"allow-x-send-file" => "enable" 

เอกสารสำหรับคุณลักษณะนี้อยู่ในวิกิ lighttpd ซึ่งจัดทำเอกสารX-LIGHTTPD-send-fileส่วนหัวไว้ แต่X-Sendfileชื่อก็ใช้ได้เช่นกัน

Nginx

เมื่อวันที่ Nginx คุณไม่สามารถใช้ส่วนหัวของคุณต้องใช้ส่วนหัวของตัวเองที่ชื่อX-Sendfile X-Accel-Redirectเปิดใช้งานโดยค่าเริ่มต้นและข้อแตกต่างที่แท้จริงเพียงอย่างเดียวคืออาร์กิวเมนต์ควรเป็น URI ไม่ใช่ระบบไฟล์ ผลที่ตามมาคือคุณต้องกำหนดตำแหน่งที่ทำเครื่องหมายว่าเป็นภายในในการกำหนดค่าของคุณเพื่อหลีกเลี่ยงไม่ให้ลูกค้าค้นหา url ของไฟล์จริงและไปที่มันโดยตรง wiki ของพวกเขามีคำอธิบายที่ดีเกี่ยวกับเรื่องนี้

Symlinks และ Location header

คุณสามารถใช้symlinkและเปลี่ยนเส้นทางไปยังไฟล์เหล่านี้ได้เพียงแค่สร้าง symlinks ไปยังไฟล์ของคุณด้วยชื่อแบบสุ่มเมื่อผู้ใช้ได้รับอนุญาตให้เข้าถึงไฟล์และเปลี่ยนเส้นทางผู้ใช้ไปยังไฟล์โดยใช้:

header("Location: " . $url_of_symlink);

เห็นได้ชัดว่าคุณต้องมีวิธีตัดแต่งเมื่อสคริปต์ที่จะสร้างถูกเรียกหรือผ่าน cron (บนเครื่องหากคุณมีการเข้าถึงหรือผ่านบริการของเว็บครอนเป็นอย่างอื่น)

ภายใต้ apache คุณต้องสามารถเปิดใช้งานFollowSymLinksใน a .htaccessหรือในการกำหนดค่า apache

การควบคุมการเข้าถึงโดย IP และส่วนหัวตำแหน่ง

การแฮ็กอีกวิธีหนึ่งคือการสร้างไฟล์การเข้าถึง apache จาก php ที่อนุญาตให้ใช้ IP ของผู้ใช้อย่างชัดเจน ภายใต้ apache หมายถึงการใช้คำสั่งmod_authz_host( mod_access)Allow from

ปัญหาคือการล็อกการเข้าถึงไฟล์ (เนื่องจากผู้ใช้หลายคนอาจต้องการทำสิ่งนี้พร้อมกัน) นั้นไม่สำคัญและอาจทำให้ผู้ใช้บางรายต้องรอเป็นเวลานาน และคุณยังคงต้องตัดไฟล์อยู่ดี

เห็นได้ชัดว่าปัญหาอื่นคือคนหลายคนที่อยู่เบื้องหลัง IP เดียวกันอาจเข้าถึงไฟล์ได้

เมื่อทุกอย่างล้มเหลว

หากคุณไม่มีวิธีใดที่จะทำให้เว็บเซิร์ฟเวอร์ของคุณสามารถช่วยคุณได้ทางออกเดียวที่เหลืออยู่คือreadfileซึ่งมีอยู่ในเวอร์ชัน php ทั้งหมดที่ใช้งานอยู่ในปัจจุบันและทำงานได้ค่อนข้างดี (แต่ไม่มีประสิทธิภาพจริงๆ)


การรวมโซลูชัน

วิธีที่ดีที่สุดในการส่งไฟล์อย่างรวดเร็วหากคุณต้องการให้โค้ด php ของคุณสามารถใช้งานได้ทุกที่คือการมีตัวเลือกที่กำหนดค่าได้จากที่ใดที่หนึ่งพร้อมคำแนะนำในการเปิดใช้งานขึ้นอยู่กับเว็บเซิร์ฟเวอร์และอาจเป็นการตรวจจับอัตโนมัติในการติดตั้งของคุณ สคริปต์

ค่อนข้างคล้ายกับสิ่งที่ทำในซอฟต์แวร์จำนวนมากสำหรับ

  • ล้าง URL ( mod_rewriteบน apache)
  • ฟังก์ชัน Crypto ( mcryptโมดูล php)
  • รองรับสตริงหลายไบต์ ( mbstringโมดูล php)

มีปัญหาในการทำงาน PHP บางอย่าง (ตรวจสอบคุกกี้ / พารามิเตอร์ GET / POST อื่น ๆ กับฐานข้อมูล) ก่อนดำเนินการheader("Location: " . $path);หรือไม่
Afriza N. Arief

2
ไม่มีปัญหาสำหรับการดำเนินการดังกล่าวสิ่งที่คุณต้องระวังคือการส่งเนื้อหา (พิมพ์เสียงสะท้อน) เนื่องจากส่วนหัวต้องมาก่อนเนื้อหาใด ๆ และทำสิ่งต่างๆหลังจากส่งส่วนหัวนี้ไม่ใช่การเปลี่ยนเส้นทางในทันทีและรหัสหลังจากนั้นจะ ดำเนินการเกือบตลอดเวลา แต่คุณไม่มีการรับประกันว่าเบราว์เซอร์จะไม่ตัดการเชื่อมต่อ
Julien Roncaglia

Jords: ฉันไม่รู้ว่า apache รองรับสิ่งนี้ด้วยฉันจะเพิ่มสิ่งนี้ในคำตอบของฉันเมื่อฉันมีเวลา ปัญหาเดียวของมันคือฉันไม่ได้รวมเป็นหนึ่ง (ตัวอย่างเช่น X-Accel-Redirect nginx) ดังนั้นจึงจำเป็นต้องใช้วิธีแก้ปัญหาที่สองหากเซิร์ฟเวอร์ไม่รองรับ แต่ฉันควรเพิ่มในคำตอบของฉัน
Julien Roncaglia

ฉันจะอนุญาตให้. htaccess ควบคุม XSendFilePath ได้ที่ไหน
Keyne Viana

1
@Keyne ฉันไม่คิดว่าคุณจะทำได้ tn123.org/mod_xsendfileไม่แสดงรายการ. htaccess ในบริบทสำหรับตัวเลือก XSendFilePath
cheshirekow

33

วิธีที่เร็วที่สุด: อย่า ดูที่ส่วนหัว x-sendfile สำหรับ nginxมีสิ่งที่คล้ายกันสำหรับเว็บเซิร์ฟเวอร์อื่น ๆ ซึ่งหมายความว่าคุณยังสามารถควบคุมการเข้าถึง ฯลฯ ใน php ได้ แต่มอบหมายการส่งไฟล์จริงไปยังเว็บเซิร์ฟเวอร์ที่ออกแบบมาเพื่อสิ่งนั้น

PS: ฉันรู้สึกหนาวสั่นเพียงแค่คิดว่าการใช้ nginx มีประสิทธิภาพมากขึ้นเพียงใดเมื่อเทียบกับการอ่านและส่งไฟล์ใน php แค่คิดว่าถ้ามีคนดาวน์โหลดไฟล์ 100 คน: ด้วย php + apache ใจกว้างนั่นอาจเป็น 100 * 15mb = 1.5GB (ประมาณยิงฉัน) ของ ram ตรงนั้น Nginx จะส่งไฟล์ไปยังเคอร์เนลจากนั้นโหลดโดยตรงจากดิสก์ลงในบัฟเฟอร์เครือข่าย เร็ว!

PPS: และด้วยวิธีนี้คุณยังสามารถควบคุมการเข้าถึงข้อมูลทั้งหมดของฐานข้อมูลที่คุณต้องการได้


4
ผมขอเพียงแค่เพิ่มว่าเรื่องนี้ก็มีอยู่สำหรับ Apache: jasny.net/articles/how-i-php-x-sendfile คุณสามารถทำให้สคริปต์คัดกรองเซิร์ฟเวอร์และส่งส่วนหัวที่เหมาะสมได้ หากไม่มีอยู่ (และผู้ใช้ไม่สามารถควบคุมเซิร์ฟเวอร์ได้ตามคำถาม) ให้ถอยกลับสู่สภาวะปกติreadfile()
Fanis Hatzidakis

ตอนนี้มันยอดเยี่ยมมาก - ฉันเกลียดการ จำกัด หน่วยความจำในโฮสต์เสมือนของฉันเสมอเพื่อที่ PHP จะให้บริการไฟล์และด้วยสิ่งนี้ฉันไม่ควรต้องทำ ฉันจะทดลองใช้เร็ว ๆ นี้
Greg W

1
และสำหรับเครดิตเมื่อครบกำหนดเครดิตLighttpdเป็นเว็บเซิร์ฟเวอร์ตัวแรกที่ใช้สิ่งนี้ (และส่วนที่เหลือก็คัดลอกมาซึ่งเป็นความคิดที่ดี แต่ให้เครดิตเมื่อครบกำหนดเครดิต) ...
ircmaxell

1
คำตอบนี้ยังคงได้รับการโหวต แต่จะไม่ทำงานในสภาพแวดล้อมที่เว็บเซิร์ฟเวอร์และการตั้งค่าอยู่นอกเหนือการควบคุมของผู้ใช้
Kirk Ouimet

คุณได้เพิ่มคำถามนั้นในคำถามของคุณจริง ๆ หลังจากที่ฉันโพสต์คำตอบนี้ และหากประสิทธิภาพเป็นปัญหาเว็บเซิร์ฟเวอร์จะต้องอยู่ในการควบคุมของคุณ
Jords

23

นี่คือโซลูชัน PHP ที่แท้จริง ฉันได้ปรับฟังก์ชันต่อไปนี้จากกรอบงานส่วนตัวของฉัน :

function Download($path, $speed = null, $multipart = true)
{
    while (ob_get_level() > 0)
    {
        ob_end_clean();
    }

    if (is_file($path = realpath($path)) === true)
    {
        $file = @fopen($path, 'rb');
        $size = sprintf('%u', filesize($path));
        $speed = (empty($speed) === true) ? 1024 : floatval($speed);

        if (is_resource($file) === true)
        {
            set_time_limit(0);

            if (strlen(session_id()) > 0)
            {
                session_write_close();
            }

            if ($multipart === true)
            {
                $range = array(0, $size - 1);

                if (array_key_exists('HTTP_RANGE', $_SERVER) === true)
                {
                    $range = array_map('intval', explode('-', preg_replace('~.*=([^,]*).*~', '$1', $_SERVER['HTTP_RANGE'])));

                    if (empty($range[1]) === true)
                    {
                        $range[1] = $size - 1;
                    }

                    foreach ($range as $key => $value)
                    {
                        $range[$key] = max(0, min($value, $size - 1));
                    }

                    if (($range[0] > 0) || ($range[1] < ($size - 1)))
                    {
                        header(sprintf('%s %03u %s', 'HTTP/1.1', 206, 'Partial Content'), true, 206);
                    }
                }

                header('Accept-Ranges: bytes');
                header('Content-Range: bytes ' . sprintf('%u-%u/%u', $range[0], $range[1], $size));
            }

            else
            {
                $range = array(0, $size - 1);
            }

            header('Pragma: public');
            header('Cache-Control: public, no-cache');
            header('Content-Type: application/octet-stream');
            header('Content-Length: ' . sprintf('%u', $range[1] - $range[0] + 1));
            header('Content-Disposition: attachment; filename="' . basename($path) . '"');
            header('Content-Transfer-Encoding: binary');

            if ($range[0] > 0)
            {
                fseek($file, $range[0]);
            }

            while ((feof($file) !== true) && (connection_status() === CONNECTION_NORMAL))
            {
                echo fread($file, round($speed * 1024)); flush(); sleep(1);
            }

            fclose($file);
        }

        exit();
    }

    else
    {
        header(sprintf('%s %03u %s', 'HTTP/1.1', 404, 'Not Found'), true, 404);
    }

    return false;
}

โค้ดมีประสิทธิภาพเท่าที่จะทำได้โดยจะปิดตัวจัดการเซสชันเพื่อให้สคริปต์ PHP อื่น ๆ สามารถทำงานพร้อมกันสำหรับผู้ใช้ / เซสชันเดียวกัน นอกจากนี้ยังรองรับการให้บริการดาวน์โหลดในช่วง (ซึ่งเป็นสิ่งที่ Apache ทำตามค่าเริ่มต้นที่ฉันสงสัย) เพื่อให้ผู้คนสามารถหยุดชั่วคราว / ดาวน์โหลดต่อและได้รับประโยชน์จากความเร็วในการดาวน์โหลดที่สูงขึ้นด้วยตัวเร่งการดาวน์โหลด นอกจากนี้ยังช่วยให้คุณสามารถระบุความเร็วสูงสุด (เป็น Kbps) ที่ควรให้บริการดาวน์โหลด (บางส่วน) ผ่าน$speedอาร์กิวเมนต์


2
เห็นได้ชัดว่านี่เป็นเพียงความคิดที่ดีหากคุณไม่สามารถใช้ X-Sendfile หรือตัวแปรตัวใดตัวหนึ่งเพื่อให้เคอร์เนลส่งไฟล์ คุณควรจะสามารถแทนที่การวนซ้ำ feof () / fread () ด้านบนด้วย [ php.net/manual/en/function.eio-sendfile.php](PHP ของ eio_sendfile ()] ซึ่งทำสิ่งเดียวกันใน PHP สิ่งนี้ไม่เร็วเท่ากับการทำในเคอร์เนลโดยตรงเนื่องจากเอาต์พุตใด ๆ ที่สร้างขึ้นใน PHP ยังคงต้องย้อนกลับไปตามกระบวนการเว็บเซิร์ฟเวอร์ แต่จะเร็วกว่าการทำในโค้ด PHP มาก
Brian C

@BrianC: แน่นอน แต่คุณไม่สามารถ จำกัด ความเร็วหรือความสามารถหลายส่วนด้วย X-Sendfile (ซึ่งอาจไม่มีให้บริการ) และeioไม่สามารถใช้งานได้ตลอดเวลา ถึงกระนั้น +1 ก็ไม่รู้เกี่ยวกับส่วนขยาย pecl นั้น =)
Alix Axel

จะมีประโยชน์หรือไม่หากสนับสนุนการเข้ารหัสการถ่ายโอน: แบบก้อนและการเข้ารหัสเนื้อหา: gzip
skibulk

ทำไม$size = sprintf('%u', filesize($path))?
Svish

14
header('Location: ' . $path);
exit(0);

ให้ Apache ทำงานแทนคุณ


12
วิธีนี้ง่ายกว่าวิธี x-sendfile แต่จะใช้ไม่ได้เพื่อ จำกัด การเข้าถึงไฟล์โดยบอกเฉพาะคนที่ล็อกอินเท่านั้น ถ้าไม่จำเป็นต้องทำก็เยี่ยมไปเลย!
คอร์ด

เพิ่มการตรวจสอบผู้อ้างอิงด้วย mod_rewrite
sanmai

1
คุณสามารถรับรองความถูกต้องก่อนที่จะส่งผ่านส่วนหัว ด้วยวิธีนี้คุณจะไม่ได้สูบสิ่งต่างๆมากมายผ่านหน่วยความจำของ PHP
Brent

7
@UltimateBrent สถานที่ยังคงต้องสามารถเข้าถึงได้ทั้งหมด .. และการตรวจสอบการอ้างอิงก็ไม่มีความปลอดภัยเลยเนื่องจากมาจากไคลเอนต์
Øyvind Skaar

@ Jimbo โทเค็นผู้ใช้ที่คุณจะตรวจสอบอย่างไร? ด้วย PHP? ทันใดนั้นการแก้ปัญหาของคุณกำลังเกิดขึ้นอีกครั้ง
Mark Amery

1

การใช้งานที่ดีขึ้นด้วยการรองรับแคชส่วนหัว http ที่กำหนดเอง

serveStaticFile($fn, array(
        'headers'=>array(
            'Content-Type' => 'image/x-icon',
            'Cache-Control' =>  'public, max-age=604800',
            'Expires' => gmdate("D, d M Y H:i:s", time() + 30 * 86400) . " GMT",
        )
    ));

function serveStaticFile($path, $options = array()) {
    $path = realpath($path);
    if (is_file($path)) {
        if(session_id())
            session_write_close();

        header_remove();
        set_time_limit(0);
        $size = filesize($path);
        $lastModifiedTime = filemtime($path);
        $fp = @fopen($path, 'rb');
        $range = array(0, $size - 1);

        header('Last-Modified: ' . gmdate("D, d M Y H:i:s", $lastModifiedTime)." GMT");
        if (( ! empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModifiedTime ) ) {
            header("HTTP/1.1 304 Not Modified", true, 304);
            return true;
        }

        if (isset($_SERVER['HTTP_RANGE'])) {
            //$valid = preg_match('^bytes=\d*-\d*(,\d*-\d*)*$', $_SERVER['HTTP_RANGE']);
            if(substr($_SERVER['HTTP_RANGE'], 0, 6) != 'bytes=') {
                header('HTTP/1.1 416 Requested Range Not Satisfiable', true, 416);
                header('Content-Range: bytes */' . $size); // Required in 416.
                return false;
            }

            $ranges = explode(',', substr($_SERVER['HTTP_RANGE'], 6));
            $range = explode('-', $ranges[0]); // to do: only support the first range now.

            if ($range[0] === '') $range[0] = 0;
            if ($range[1] === '') $range[1] = $size - 1;

            if (($range[0] >= 0) && ($range[1] <= $size - 1) && ($range[0] <= $range[1])) {
                header('HTTP/1.1 206 Partial Content', true, 206);
                header('Content-Range: bytes ' . sprintf('%u-%u/%u', $range[0], $range[1], $size));
            }
            else {
                header('HTTP/1.1 416 Requested Range Not Satisfiable', true, 416);
                header('Content-Range: bytes */' . $size);
                return false;
            }
        }

        $contentLength = $range[1] - $range[0] + 1;

        //header('Content-Disposition: attachment; filename="xxxxx"');
        $headers = array(
            'Accept-Ranges' => 'bytes',
            'Content-Length' => $contentLength,
            'Content-Type' => 'application/octet-stream',
        );

        if(!empty($options['headers'])) {
            $headers = array_merge($headers, $options['headers']);
        }
        foreach($headers as $k=>$v) {
            header("$k: $v", true);
        }

        if ($range[0] > 0) {
            fseek($fp, $range[0]);
        }
        $sentSize = 0;
        while (!feof($fp) && (connection_status() === CONNECTION_NORMAL)) {
            $readingSize = $contentLength - $sentSize;
            $readingSize = min($readingSize, 512 * 1024);
            if($readingSize <= 0) break;

            $data = fread($fp, $readingSize);
            if(!$data) break;
            $sentSize += strlen($data);
            echo $data;
            flush();
        }

        fclose($fp);
        return true;
    }
    else {
        header('HTTP/1.1 404 Not Found', true, 404);
        return false;
    }
}

0

หากคุณมีความเป็นไปได้ที่จะเพิ่มนามสกุล PECL ลงใน php ของคุณคุณสามารถใช้ฟังก์ชันจากแพ็คเกจ Fileinfoเพื่อกำหนดประเภทเนื้อหาจากนั้นส่งส่วนหัวที่เหมาะสม ...


/ คุณเคยพูดถึงความเป็นไปได้นี้หรือไม่? :)
Andreas Linden

0

Downloadฟังก์ชันPHP ที่กล่าวถึงในที่นี้ทำให้เกิดความล่าช้าก่อนที่ไฟล์จะเริ่มดาวน์โหลดจริง ผมไม่ทราบว่านี้เกิดจากการใช้แคชเคลือบเงาหรืออะไร แต่สำหรับฉันมันช่วยในการลบsleep(1);อย่างสมบูรณ์และชุดที่จะ$speed 1024ตอนนี้ใช้งานได้โดยไม่มีปัญหาใด ๆ เร็วเท่านรก บางทีคุณอาจปรับเปลี่ยนฟังก์ชั่นนั้นได้ด้วยเพราะฉันเห็นมันใช้กันทั่วอินเทอร์เน็ต


0

ฉันเขียนโค้ดฟังก์ชั่นที่เรียบง่ายมากเพื่อให้บริการไฟล์ด้วย PHP และการตรวจจับประเภท MIME อัตโนมัติ:

function serve_file($filepath, $new_filename=null) {
    $filename = basename($filepath);
    if (!$new_filename) {
        $new_filename = $filename;
    }
    $mime_type = mime_content_type($filepath);
    header('Content-type: '.$mime_type);
    header('Content-Disposition: attachment; filename="downloaded.pdf"');
    readfile($filepath);
}

การใช้งาน

serve_file("/no_apache/invoice243.pdf");
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.