PHP का उपयोग कर एक फ़ाइल की सेवा करने का सबसे तेज़ तरीका




performance file-io (6)

कैश समर्थन के साथ एक बेहतर कार्यान्वयन, अनुकूलित 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;
    }
}

मैं एक ऐसे फ़ंक्शन को एक साथ रखने की कोशिश कर रहा हूं जो फ़ाइल पथ प्राप्त करता है, यह पहचानता है कि यह क्या है, उचित शीर्षलेख सेट करता है, और अपाचे की तरह ही इसकी सेवा करता है।

कारण मैं ऐसा कर रहा हूं क्योंकि मुझे फ़ाइल की सेवा करने से पहले अनुरोध के बारे में कुछ जानकारी संसाधित करने के लिए PHP का उपयोग करने की आवश्यकता है।

गति महत्वपूर्ण है

आभासी () एक विकल्प नहीं है

साझा होस्टिंग वातावरण में काम करना चाहिए जहां उपयोगकर्ता का वेब सर्वर (अपाचे / 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], '.');
    }
}
}
?>

यदि आप छिपाना चाहते हैं कि फ़ाइल कहां स्थित है और विशिष्ट विशेषाधिकार वाले लोग फ़ाइल डाउनलोड कर सकते हैं तो PHP का उपयोग रिले के रूप में करना एक अच्छा विचार है, और आपको अधिक सुरक्षा और नियंत्रण प्राप्त करने के लिए कुछ CPU समय बलिदान करना होगा।


यहां एक शुद्ध 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 स्क्रिप्ट एक ही उपयोगकर्ता / सत्र के लिए एक साथ चल सकें। यह श्रेणियों में डाउनलोड की सेवा का भी समर्थन करता है (जो अपाचे द्वारा डिफ़ॉल्ट रूप से अपाचे करता है), ताकि लोग डाउनलोड को रोक / फिर से शुरू कर सकें और डाउनलोड त्वरक के साथ उच्च डाउनलोड गति से भी लाभ उठा सकें। यह आपको अधिकतम गति (केबीपीएस में) निर्दिष्ट करने की अनुमति देता है जिस पर $speed तर्क के माध्यम से डाउनलोड (भाग) परोसा जाना चाहिए।


यहां वर्णित PHP Download फ़ंक्शन वास्तव में फ़ाइल डाउनलोड करने से पहले कुछ देरी कर रहा था। मुझे नहीं पता कि यह वार्निश कैश या क्या उपयोग कर रहा था, लेकिन मेरे लिए यह sleep(1); को हटाने में मदद मिली sleep(1); पूरी तरह से और 1024 $speed 1024 सेट करें। अब यह नरक के रूप में तेजी से किसी भी समस्या के बिना काम करता है। हो सकता है कि आप उस फ़ंक्शन को भी संशोधित कर सकें, क्योंकि मैंने इसे पूरे इंटरनेट पर उपयोग किया था।


मेरा पिछला उत्तर आंशिक था और अच्छी तरह से प्रलेखित नहीं था, यहां से समाधानों के सारांश और चर्चा में दूसरों के समाधान के साथ एक अद्यतन है।

समाधानों को सर्वोत्तम समाधान से सबसे खराब समाधान के लिए आदेश दिया जाता है, लेकिन समाधान से भी वेब सर्वर पर अधिक नियंत्रण की आवश्यकता होती है जिसे कम की आवश्यकता होती है। ऐसा कोई समाधान नहीं है जो एक समाधान है जो दोनों तेज़ और हर जगह काम करता है।

एक्स-सेंडफाइल हेडर का उपयोग करना

दूसरों द्वारा दस्तावेज के रूप में यह वास्तव में सबसे अच्छा तरीका है। आधार यह है कि आप 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) की आवश्यकता है।

अमरीका की एक मूल जनजाति

अपाचे के तहत यदि आप 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-LIGHTTPD-send-file नाम भी काम करता है

nginx

Nginx पर आप X-Sendfile शीर्षलेख का उपयोग नहीं कर सकते हैं, आपको अपने स्वयं के शीर्षलेख का उपयोग करना होगा जिसका नाम X-Accel-Redirect । यह डिफ़ॉल्ट रूप से सक्षम है और केवल वास्तविक अंतर यह है कि यह तर्क एक यूआरआई फ़ाइल सिस्टम नहीं होना चाहिए। नतीजा यह है कि आपको वास्तविक कॉन्फ़िगरेशन यूआरएल ढूंढने और सीधे उस पर जाने से बचने के लिए अपनी कॉन्फ़िगरेशन में आंतरिक के रूप में चिह्नित स्थान को परिभाषित करना होगा, उनके विकी में इसका एक अच्छा स्पष्टीकरण है।

सिम्लिंक और स्थान शीर्षलेख

आप symlinks उपयोग कर सकते हैं और उन्हें रीडायरेक्ट कर सकते हैं, बस किसी फ़ाइल को एक्सेस करने के लिए अधिकृत होने पर उपयोगकर्ता को रीडायरेक्ट नामों के साथ सिमलिंक बनाएं और उपयोगकर्ता को इसका उपयोग करके रीडायरेक्ट करें:

header("Location: " . $url_of_symlink);

स्पष्ट रूप से आपको उन्हें छीनने का एक तरीका होगा जब स्क्रिप्ट उन्हें बनाने के लिए या क्रॉन के माध्यम से (मशीन पर यदि आपके पास पहुंच है या अन्य वेबक्रोन सेवा के माध्यम से अन्यथा)

अपाचे के तहत आपको .htaccess या apache config में FollowSymLinks को सक्षम करने में सक्षम होना चाहिए।

आईपी ​​और स्थान शीर्षलेख द्वारा अभिगम नियंत्रण

एक और हैक स्पष्ट उपयोगकर्ता आईपी की अनुमति देने वाले PHP से अपाचे एक्सेस फ़ाइलों को उत्पन्न करना है। अपाचे के तहत इसका मतलब है mod_access ( mod_access ) कमांड Allow from

समस्या यह है कि फ़ाइल तक पहुंच को लॉक करना (जैसा कि एकाधिक उपयोगकर्ता एक ही समय में ऐसा करना चाहते हैं) गैर तुच्छ है और कुछ उपयोगकर्ताओं को लंबे समय तक प्रतीक्षा कर सकता है। और आपको अभी भी फाइल को फिर से छीनने की जरूरत है।

जाहिर है एक और समस्या यह होगी कि एक ही आईपी के पीछे कई लोग संभावित रूप से फ़ाइल तक पहुंच सकते हैं।

जब सब कुछ विफल हो जाता है

यदि आपके पास वास्तव में आपकी वेब सर्वर को आपकी सहायता करने का कोई तरीका नहीं है, तो शेष समाधान केवल readfile यह वर्तमान में उपयोग में आने वाले सभी PHP संस्करणों में उपलब्ध है और बहुत अच्छी तरह से काम करता है (लेकिन वास्तव में कुशल नहीं है)।

समाधान संयोजन

ठीक है, यदि आप अपने PHP कोड को हर जगह प्रयोग करने योग्य बनाना चाहते हैं तो फ़ाइल को वास्तव में तेज़ भेजने का सबसे अच्छा तरीका है कि वेब सर्वर के आधार पर इसे सक्रिय करने के तरीके के बारे में निर्देशों के साथ कहीं और कॉन्फ़िगर करने योग्य विकल्प होना चाहिए और शायद आपके इंस्टॉल में एक ऑटो डिटेक्शन स्क्रिप्ट।

यह बहुत सारे सॉफ़्टवेयर में किए गए कार्यों के समान ही है

  • स्वच्छ यूआरएल (apache पर mod_rewrite )
  • क्रिप्टो फ़ंक्शंस ( mcrypt php मॉड्यूल)
  • मल्टीबाइट स्ट्रिंग सपोर्ट ( mbstring php मॉड्यूल)

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

अपाचे को आपके लिए काम करने दें।





x-sendfile