使用PHP服务文件的最快方法

问题描述 投票:96回答:8

我试图将一个接收文件路径的函数放在一起,确定它是什么,设置适当的标头,然后像Apache一样提供它。

之所以这样做,是因为在提供文件之前,我需要使用PHP处理有关请求的某些信息。

速度很关键

virtual()不可选项

必须在用户无法控制Web服务器(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], '.');
    }
}
}
?>

我正在尝试将一个函数接收文件路径,确定它是什么,设置适当的标头,并像Apache一样提供它。我这样做的原因是因为我...

php performance file-io x-sendfile
8个回答
139
投票

我以前的回答是不完整的,没有得到充分的记录,这里是更新,其中总结了该解决方案以及讨论中的其他解决方案。


33
投票

最快的方法:不要。查看mod_rewrite,其他Web服务器也有类似的内容。这意味着您仍然可以在php中进行访问控制等,但是将文件的实际发送委托给为此设计的Web服务器。

P.S:我不寒而栗,只是想想与在php中读取和发送文件相比,将它与nginx一起使用效率更高。试想一下,如果有100个人正在下载文件:使用php + apache,那么慷慨,那大概是100 * 15mb = 1.5GB(大约,给我射击),就在那儿。 Nginx只会将文件发送到内核,然后将其直接从磁盘加载到网络缓冲区中。快速!

P.P.S:而且,通过这种方法,您仍然可以执行所有访问控制所需的数据库工作。


22
投票

这里有一个纯PHP解决方案。我已经适应了以下功能mcrypt

mbstring

该代码效率极高,它会关闭会话处理程序,以便其他PHP脚本可以为同一用户/会话同时运行。它还支持在一定范围内提供下载服务(我怀疑这也是Apache的默认做法),因此人们可以暂停/恢复下载,还可以通过下载加速器受益于更高的下载速度。它还允许您通过x-sendfile header for nginx参数指定下载文件(部分)的最大速度(以Kbps为单位)。



1
投票

一个更好的实现,具有缓存支持,自定义的HTTP标头。


0
投票

如果您有可能向您的PHP添加PECL扩展名,则可以简单地使用$speed中的函数来确定内容类型,然后发送适当的标头...


0
投票

此处提到的PHP header('Location: ' . $path); exit(0); 函数在实际开始下载文件之前造成了一些延迟。我不知道这是由于使用清漆缓存引起的还是由什么引起的,但是对我来说,这有助于完全删除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; } } 并将Fileinfo package设置为Download。现在,它可以像地狱般快速地工作,而没有任何问题。也许您也可以修改该功能,因为我看到它在整个Internet上都使用过。


0
投票

我编写了一个非常简单的函数,可以通过PHP和自动MIME类型检测来提供文件:

sleep(1);
© www.soinside.com 2019 - 2024. All rights reserved.