如何通过PHP脚本下载大文件


How to download large files through PHP script

使用PHP,我试图提供由于授权问题而不在web可访问目录中的大文件(可能高达200MB(。目前,我使用readfile()调用和一些标头来为文件提供服务,但PHP似乎在发送文件之前将其加载到内存中。我打算部署在共享托管服务器上,这将不允许我使用太多内存或添加自己的Apache模块,如X-Sendfile。

出于安全原因,我不能让我的文件位于网络可访问的目录中。有人知道一种内存密集度较低的方法吗?我可以在共享托管服务器上部署这种方法?

编辑:

if(/* My authorization here */) {
        $path = "/uploads/";
        $name = $row[0];           //This is a MySQL reference with the filename
        $fullname = $path . $name; //Create filename
        $fd = fopen($fullname, "rb");
        if ($fd) {
            $fsize = filesize($fullname);
            $path_parts = pathinfo($fullname);
            $ext = strtolower($path_parts["extension"]);
            switch ($ext) {
                case "pdf":
                header("Content-type: application/pdf");
                break;
                case "zip":
                header("Content-type: application/zip");
                break;
                default:
                header("Content-type: application/octet-stream");
                break;
            }
            header("Content-Disposition: attachment; filename='"".$path_parts["basename"]."'"");
            header("Content-length: $fsize");
            header("Cache-control: private"); //use this to open files directly
            while(!feof($fd)) {
                $buffer = fread($fd, 1*(1024*1024));
                echo $buffer;
                ob_flush();
                flush();    //These two flush commands seem to have helped with performance
            }
        }
        else {
            echo "Error opening file";
        }
        fclose($fd);

如果使用fopenfread而不是readfile,应该可以解决您的问题。

PHP的readfile文档中有一个解决方案,展示了如何使用fread来执行您想要的操作。

要从服务器下载大文件,我在php.ini文件中更改了以下设置:

Upload_max_filesize  - 1500 M
Max_input_time  - 1000
Memory_limit    - 640M
Max_execution_time -  1800
Post_max_size - 2000 M

现在,我可以在服务器上上传和下载175MB的视频。从那以后,我有了专用服务器。所以,做出这些改变很容易。

下面是下载该文件的PHP脚本。对于大文件大小,我没有对此代码段进行任何更改。

// Begin writing headers
            ob_clean(); // Clear any previously written headers in the output buffer
            if($filetype=='application/zip')
            {
                if(ini_get('zlib.output_compression'))
                    ini_set('zlib.output_compression', 'Off');
                $fp = @fopen($filepath, 'rb'); 
                if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE"))
                {
                    header('Content-Type: "$content_type"');
                    header('Content-Disposition: attachment; filename="'.$filename.'"');
                    header('Expires: 0');
                    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
                    header("Content-Transfer-Encoding: binary");
                    header('Pragma: public');
                    header("Content-Length: ".filesize(trim($filepath)));
                }
                else
                {
                    header('Content-Type: "$content_type"');
                    header('Content-Disposition: attachment; filename="'.$filename.'"');
                    header("Content-Transfer-Encoding: binary");
                    header('Expires: 0');
                    header('Pragma: no-cache');
                    header("Content-Length: ".filesize(trim($filepath)));
                }
                fpassthru($fp);
                fclose($fp);
            }
            elseif($filetype=='audio'|| $filetype=='video')
            { 
                global $mosConfig_absolute_path,$my;
                ob_clean();
                header("Pragma: public");
                header('Expires: 0');
                header('Cache-Control: no-store, no-cache, must-revalidate');
                header('Cache-Control: pre-check=0, post-check=0, max-age=0');
                header("Cache-Control: public");    
                header("Content-Description: File Transfer");
                header("Content-Type: application/force-download"); 
                header("Content-Type: $content_type");
                header("Content-Length: ".filesize(trim($filepath)));
                header("Content-Disposition: attachment; filename='"$filename'"");
                // Force the download           
                header("Content-Transfer-Encoding: binary");            
                @readfile($filepath);
            }
              else{ // for all other types of files except zip,audio/video
                ob_clean();
                header("Pragma: public");
                header('Expires: 0');
                header('Cache-Control: no-store, no-cache, must-revalidate');
                header('Cache-Control: pre-check=0, post-check=0, max-age=0');
                header("Cache-Control: public");    
                header("Content-Description: File Transfer");
                header("Content-Type: $content_type");
                header("Content-Length: ".filesize(trim($filepath)));
                header("Content-Disposition: attachment; filename='"$filename'"");
                // Force the download           
                header("Content-Transfer-Encoding: binary");            
                @readfile($filepath);       
            }
            exit;

如果您关心性能,可以在apache、nginx和lighttpd中使用xsendfile作为模块。检查readfile()文档的用户注释。

这些网络服务器也有一些模块,它们接受带有额外哈希值的url,从而允许在短时间内下载文件。这也可以用于解决授权问题。

您也可以用Gordian Knot的风格来处理这个问题——也就是说,完全避开这个问题。将文件保存在不可访问的目录中,当下载启动时,您可以简单地进行

$tempstring = rand();
symlink('/filestore/filename.extension', '/www/downloads' . $tempstring . '-filename.extension');
echo('Your download is available here: <a href="/downloads/' . $tempstring . '-filename.extension">');

并将cronjob设置为CCD_ 8任何超过10分钟的下载链接。实际上,不需要处理您的数据,也不需要对HTTP头进行按摩等。

甚至有几个图书馆就是为了这个目的。