草庐IT

php - 使用 PHP 提供文件的最快方法

coder 2023-04-24 原文

我正在尝试组合一个函数,该函数接收文件路径、识别它是什么、设置适当的 header 并像 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], '.');
    }
}
}
?>

最佳答案

我之前的回答是部分的并且没有很好的记录,这里是一个更新,其中包含来自它和讨论中其他人的解决方案的摘要。

解决方案从最好的解决方案到最差的解决方案排序,但也从需要对 Web 服务器进行最多控制的解决方案到需要较少的解决方案。似乎没有一种简单的方法可以让一个解决方案既快速又适用于任何地方。

使用 X-SendFile header

正如其他人所记录的那样,这实际上是最好的方法。基础是您在 php 中进行访问控制,然后您不是自己发送文件,而是告诉 Web 服务器执行此操作。

基本的php代码是:

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

哪里$file_name是文件系统上的完整路径。

这个解决方案的主要问题是它需要被 web 服务器允许并且默认情况下没有安装 (apache)、默认情况下不活动 (lighttpd) 或需要特定配置 (nginx)。

Apache

在apache下,如果你使用mod_php,你需要安装一个名为mod_xsendfile的模块。然后配置它(在 apache 配置或 .htaccess 中,如果你允许的话)
XSendFile on
XSendFilePath /home/www/example.com/htdocs/files/

使用此模块,文件路径可以是绝对的,也可以是相对于指定的 XSendFilePath .

轻量级

mod_fastcgi 在配置时支持这个
"allow-x-send-file" => "enable" 

该功能的文档位于 lighttpd wiki 上他们记录了 X-LIGHTTPD-send-file标题但 X-Sendfile名字也有用

nginx

在 Nginx 上你不能使用 X-Sendfile header 必须使用自己的 header ,名为 X-Accel-Redirect .它默认启用,唯一真正的区别是它的参数应该是 URI 而不是文件系统。结果是您必须在配置中定义一个标记为内部的位置,以避免客户端找到真正的文件 url 并直接访问它,他们的 wiki 包含 a good explanation这个的。

符号链接(symbolic link)和位置标题

您可以使用 symlinks并重定向到它们,当用户被授权访问文件并使用以下命令将用户重定向到该文件时,只需使用随机名称创建指向文件的符号链接(symbolic link):
header("Location: " . $url_of_symlink);

显然,当调用创建它们的脚本或通过 cron(在机器上,如果您有权访问或通过某些 webcron 服务)时,您将需要一种方法来修剪它们

在 apache 下你需要能够启用 FollowSymLinks .htaccess 或在 apache 配置中。

通过 IP 和 Location header 进行访问控制

另一个黑客是从允许显式用户 IP 的 php 生成 apache 访问文件。在 apache 下,这意味着使用 mod_authz_host ( mod_access ) Allow from命令。

问题在于锁定对文件的访问(因为多个用户可能希望同时执行此操作)并非易事,并且可能导致某些用户等待很长时间。无论如何,您仍然需要修剪文件。

显然,另一个问题是同一 IP 背后的多个人可能会访问该文件。

当其他一切都失败时

如果你真的没有任何办法让你的网络服务器来帮助你,剩下的唯一解决方案是 readfile它在当前使用的所有 php 版本中都可用,并且运行良好(但效率不高)。

组合解决方案

好吧,如果您希望您的 php 代码在任何地方都可用,那么真正快速发送文件的最佳方法是在某处有一个可配置的选项,并附有有关如何根据 Web 服务器激活它的说明,并且可能在您的安装中进行自动检测脚本。

它与许多软件中所做的非常相似
  • 清理网址( mod_rewrite 在 apache 上)
  • 加密函数(mcrypt php 模块)
  • 多字节字符串支持(mbstring php 模块)
  • 关于php - 使用 PHP 提供文件的最快方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3697748/

    有关php - 使用 PHP 提供文件的最快方法的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    4. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    5. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    6. ruby - 在 Ruby 中使用匿名模块 - 2

      假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

    7. ruby - 其他文件中的 Rake 任务 - 2

      我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

    8. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

      我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

    9. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

      我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

    10. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

      我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

    随机推荐