Kir*_*met 93 php performance file-io x-sendfile
我正在尝试组合一个接收文件路径的函数,识别它是什么,设置适当的头,并像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], '.');
}
}
}
?>
Run Code Online (Sandbox Code Playgroud)
Jul*_*lia 135
我之前的回答是不完整的,没有详细记录,这是一个更新,其中包含了解决方案的摘要以及讨论中的其他解决方案.
解决方案从最佳解决方案到最差解决方案,也从需要最大程度控制Web服务器的解决方案到需要更少控制的解决方案.似乎没有一种简单的方法可以让一个既快速又无处不在的解决方案.
正如其他人所记录的那样,它实际上是最好的方式.基础是你在php中进行访问控制,然后自己发送文件而不是自己发送文件,告诉Web服务器这样做.
基本的PHP代码是:
header("X-Sendfile: $file_name");
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($file_name) . '"');
Run Code Online (Sandbox Code Playgroud)
$file_name
文件系统上的完整路径在哪里.
此解决方案的主要问题是它需要Web服务器允许,默认情况下不安装(apache),默认情况下不活动(lighttpd)或需要特定配置(nginx).
在apache下如果你使用mod_php你需要安装一个名为mod_xsendfile的模块然后配置它(如果你允许的话,可以在apache config或.htaccess中)
XSendFile on
XSendFilePath /home/www/example.com/htdocs/files/
Run Code Online (Sandbox Code Playgroud)
使用此模块,文件路径可以是绝对路径,也可以是指定路径XSendFilePath
.
配置时,mod_fastcgi支持此功能
"allow-x-send-file" => "enable"
Run Code Online (Sandbox Code Playgroud)
该功能的文档位于lighttpd wiki上,它们记录了X-LIGHTTPD-send-file
标题,但X-Sendfile
名称也有效
在Nginx上,您无法使用X-Sendfile
标头,您必须使用自己的名称标头X-Accel-Redirect
.它默认启用,唯一真正的区别是它的参数应该是URI而不是文件系统.结果是您必须在配置中定义标记为内部的位置,以避免客户端找到真实的文件URL并直接转到它,他们的wiki包含对此的一个很好的解释.
您可以使用符号链接并重定向到它们,只需在用户被授权访问文件并使用以下方法将用户重定向到该文件时,使用随机名称为您的文件创建符号链接:
header("Location: " . $url_of_symlink);
Run Code Online (Sandbox Code Playgroud)
显然,你需要一种方法来修剪它们,无论是调用创建它们的脚本还是通过cron(如果你有访问权限,则在机器上或通过某些webcron服务)
在Apache你需要能够使FollowSymLinks
在.htaccess
或Apache的配置.
另一个黑客是从php生成apache访问文件,允许显式用户IP.在apache下它意味着using mod_authz_host
(mod_access
)Allow from
命令.
问题是锁定对文件的访问(因为多个用户可能希望同时执行此操作)是非常重要的,并且可能导致一些用户等待很长时间.而且你仍然需要修剪文件.
显然,另一个问题是同一IP背后的多个人可能会访问该文件.
如果你真的没有办法让你的网络服务器帮助你,剩下的唯一解决方案是readfile,它可以在当前使用的所有php版本中使用并且工作得很好(但效率不高).
好吧,如果你希望你的PHP代码可以在任何地方使用,那么发送文件的最好方法是在某个地方有一个可配置的选项,有关如何激活它的说明,具体取决于web服务器,也许在你的安装中自动检测脚本.
它与许多软件中的内容非常相似
mod_rewrite
在apache上)mcrypt
php模块)mbstring
php模块)Jor*_*rds 33
最快的方法:不要.查看nginx的x-sendfile头,其他Web服务器也有类似的东西.这意味着您仍然可以在php中进行访问控制等,但将实际发送的文件委托给为此设计的Web服务器.
PS:与在php中读取和发送文件相比,我在考虑使用nginx时效率更高,我感到畏缩.试想是否有100个人正在下载文件:使用php + apache,慷慨,那可能是100*15mb = 1.5GB(约,拍我),ram就在那里.Nginx只是将文件发送到内核,然后直接从磁盘加载到网络缓冲区.迅速!
PPS:并且,使用此方法,您仍然可以执行所需的所有访问控制,数据库内容.
Ali*_*xel 22
这是一个纯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;
}
Run Code Online (Sandbox Code Playgroud)
代码尽可能高效,它关闭会话处理程序,以便其他PHP脚本可以同时为同一个用户/会话运行.它还支持在范围内提供下载服务(这也是我默认的Apache所做的事情),因此人们可以暂停/恢复下载,并且还可以通过下载加速器获得更高的下载速度.它还允许您指定应通过$speed
参数提供下载(部分)的最大速度(以Kbps为单位).
amp*_*ine 13
header('Location: ' . $path);
exit(0);
Run Code Online (Sandbox Code Playgroud)
让Apache为您完成工作.