6

我正在使用强制下载来下载我所做的网站(http://pr1pad.kissyour.net)上的大部分 zip 和 mp3 - 以跟踪谷歌分析、数据库中的下载并隐藏真实的下载路径:

是这样的:

extending CI model

... - bunch of code

function _fullread ($sd, $len) {
 $ret = '';
 $read = 0;
 while ($read < $len && ($buf = fread($sd, $len - $read))) {
  $read += strlen($buf);
  $ret .= $buf;
 }
 return $ret;
}

function download(){    
    /* DOWNLOAD ITSELF */

    ini_set('memory_limit', '160M');
    apache_setenv('no-gzip', '1');
    ob_end_flush();

    header("Pragma: public");
    header("Expires: 0");
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header("Cache-Control: public",FALSE);
    header("Content-Description: File Transfer");
    header("Content-type: application/octet-stream");
     if (isset($_SERVER['HTTP_USER_AGENT']) && 
      (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false))
      header('Content-Type: application/force-download'); //IE HEADER
    header("Accept-Ranges: bytes");
    header("Content-Disposition: attachment; filename=\"" . basename("dir-with-    files/".$filename) . "\";");
    header("Content-Transfer-Encoding: binary");
    header("Content-Length: " . filesize("dir-with-files/".$filename));

    // Send file for download
    if ($stream = fopen("dir-with-files/$filename", 'rb')){
     while(!feof($stream) && connection_status() == 0){
      //reset time limit for big files
      set_time_limit(0);
      print($this->_fullread($stream,1024*16));
      flush();
     }
     fclose($stream);
    }
}

它在带有 CI 1.7.2 的 LAMP 上 - 这是我自己的方法,来自互联网上的各种方法,因为在开发过程中,出现了这些问题: -服务器限制ini_set没有帮助,所以我使用缓冲_fullread而不是正常fread,它被用于@readonly - ob_end_flush(),因为站点是在 CI1.7.2 中完成的,我需要清理缓冲区

现在......它不起作用。它确实如此,然后它停止显示预期的大小/下载时间 - 我试图清理它,当我清理代码时,发生了一些事情,我不知道在任何以前的版本中是什么 - 它没有工作(没有更改设置) -编辑:不工作=将所有内容输出到浏览器窗口。

所以我说,去他妈的,我会看这里。

所以,我基本上是在寻找脚本或函数,我可以把它放到我的输出模型中并且会做:

  • 调用 force-download(在 Chrome 中开始下载,在 IE、FF、Safari 中打开 modal open/save/cancel)
  • 显示文件大小和估计的 dl 时间(这取决于浏览器,我知道,但首先,浏览器必须知道文件大小
  • 在 PC + Mac 上的 IE6、7、8、FF3、Opera、Chrome & 和 safari 中工作(测试并确认!)
  • 在服务器上,我也有 56MB 内存限制,我无法添加,所以这也很重要

先感谢您。

编辑:现在我感觉比以前/以前更糟了,因为我试图用 .htaccess 强制下载 - 虽然它有效,但它几乎没有小/主要(选择你的)问题

  • 它显示了完整的路径(对我来说是次要的)
  • 它等到整个下载完成(显示为“正在连接”),然后只显示它正在下载 - 并在一秒钟内下载(对我来说很重要)

现在,虽然我删除了.htaccess,但它仍然等到下载完成(就像它首先下载到缓存一样),它只是获取connected并显示打开/保存对话框。

4

7 回答 7

8

所以,我使用了这段代码(它是在互联网上找到的可恢复 http 下载的修改版本)

function _output_file($file, $path)
{
    $size = filesize($path.$file);

    @ob_end_clean(); //turn off output buffering to decrease cpu usage

    // required for IE, otherwise Content-Disposition may be ignored
    if(ini_get('zlib.output_compression'))
    ini_set('zlib.output_compression', 'Off');

    header('Content-Type: application/force-download');
    header('Content-Disposition: attachment; filename="'.basename($file).'"');
    header("Content-Transfer-Encoding: binary");
    header('Accept-Ranges: bytes');

    /* The three lines below basically make the 
    download non-cacheable */
    header("Cache-control: no-cache, pre-check=0, post-check=0");
    header("Cache-control: private");
    header('Pragma: private');
    header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");

    // multipart-download and download resuming support
    if(isset($_SERVER['HTTP_RANGE']))
    {
        list($a, $range) = explode("=",$_SERVER['HTTP_RANGE'],2);
        list($range) = explode(",",$range,2);
        list($range, $range_end) = explode("-", $range);
        $range=intval($range);
        if(!$range_end) {
            $range_end=$size-1;
        } else {
            $range_end=intval($range_end);
        }

        $new_length = $range_end-$range+1;
        header("HTTP/1.1 206 Partial Content");
        header("Content-Length: $new_length");
        header("Content-Range: bytes $range-$range_end/$size");
    } else {
        $new_length=$size;
        header("Content-Length: ".$size);
    }

    /* output the file itself */
    $chunksize = 1*(1024*1024); //you may want to change this
    $bytes_send = 0;
    if ($file = fopen($path.$file, 'rb'))
    {
        if(isset($_SERVER['HTTP_RANGE']))
        fseek($file, $range);

        while
            (!feof($file) && 
             (!connection_aborted()) && 
             ($bytes_send<$new_length) )
        {
            $buffer = fread($file, $chunksize);
            print($buffer); //echo($buffer); // is also possible
            flush();
            $bytes_send += strlen($buffer);
        }
    fclose($file);
    } else die('Error - can not open file.');

die();
}

然后在模型中:

function download_file($filename){
    /*
        DOWNLOAD
    */
    $path = "datadirwithmyfiles/"; //directory

    //track analytics

    include('includes/Galvanize.php'); //great plugin
    $GA = new Galvanize('UA-XXXXXXX-7');
    $GA->trackPageView();

    $this->_output_file($filename, $path);

}

它在 Win / MAC 上所有提到的浏览器中都可以正常工作 - 到目前为止,它没有问题。

于 2010-02-10T21:11:57.900 回答
2

好的,这是一个老问题,亚当已经接受了他自己的答案,所以大概是他自己解决了这个问题,但他没有解释为什么会这样。我注意到的一件事是他使用标题的问题:

header("Pragma: public");
header("Cache-Control: public",FALSE);

而在他使用的解决方案中:

header("Cache-control: private");
header('Pragma: private');

他没有解释为什么要更改这些,但我怀疑这与 SSL 的使用有关。我最近在需要启用 HTTP 和 HTTPS 下载的软件中解决了一个类似问题,使用以下内容添加正确的标头:

if(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) {
    header("Cache-control: private");
    header('Pragma: private');
} else {
    header('Pragma: public');
}

希望有人会发现此答案中的信息是对上述内容的有用补充。

于 2011-06-23T10:22:20.157 回答
1

有一件事我觉得很奇怪:你ob_end_flush()在函数的开头调用。这实际上会清理输出缓冲区,但它也会首先将所有内容输出到客户端(我假设包括由 CodeIgniter 设置的 Content-Headers)。将调用更改为ob_end_clean(),它会清除缓冲区并丢弃它。这将为您生成自己的标头提供一个干净的开始。

另一个提示:

您可以尝试使用此函数,而不是将文件作为流读取并逐块传递:

// ...
if (file_exists("dir-with-files/$filename")) {
   readfile($file);
}

这几乎可以解决所有问题。

于 2010-02-08T19:13:38.830 回答
0

print($this->_fullread($stream,1024*16));

我假设 _fullread 在一个班级内?如果代码看起来像上面那样,那么$this->就行不通了。

如果您注释掉所有标题内容,它是否会将文件内容输出到屏幕?

于 2010-02-08T17:24:53.633 回答
0

只是在黑暗中开枪...我在“强制下载”代码中发送的每个标头(未经过您的测试)都与您的相同,除了我调用: header("Cache-Control: private" ,错误的);

而不是: header("Cache-Control: public",FALSE);

我不知道这是否会有所帮助。

于 2010-02-08T17:30:36.663 回答
0

如果您要执行这种“使用 php 回显”方法,那么您将无法向用户显示剩余时间或预期大小。为什么?因为如果浏览器尝试在中间恢复下载,您将无法在 PHP 中处理这种情况。

如果你有一个正常的文件下载,Apache 能够支持通过 HTTP 恢复下载,但在下载暂停的情况下,当客户端请求下一个块时,Apache 无法确定脚本在哪里执行。

本质上,当浏览器暂停下载时,它将完全终止与网络服务器的连接。当您继续下载时,连接会重新打开,并且请求包含一个标志,上面写着“从字节数 X 开始”。但是对于查看您上面的 PHP 的网络服务器,字节 X 来自哪里?

虽然理论上服务器可能会在下载中断的情况下确定从哪里恢复您的脚本,但 Apache 不会尝试找出从哪里恢复。结果,发送到浏览器的标头声明服务器不支持恢复,这会在大多数主要浏览器中关闭预期的文件大小和时间限制项。

编辑:您似乎可以处理这种情况,但是您需要大量代码。请参阅http://www.php.net/manual/en/function.fread.php#84115

于 2010-02-08T18:50:47.313 回答
0

与其试图向外界隐藏您的下载路径,不如使其无法从外部访问,而只能使用上述脚本访问文件。为此,您需要在目录中放置一个 htaccess 文件(名为“.htaccess”的文本文件,不要忘记前导点)。htaccess 的内容是这样的:

order deny,allow
deny from all
allow from localhost

现在尝试从 *world 访问路径将使网络服务器创建 401 禁止。

通过默默无闻的安全性不是你想要的。

于 2014-03-01T14:57:07.413 回答