使用PHP和nginx X-Accel-Redirect提供大文件服务

6

你好,我希望用户能够从我配置有nginx PHP的Windows服务器下载PDF文件。这是我的nginx.conf(服务器块):

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.php;

        }

         location /download {
            internal;
            alias /protected;
        }
    }
}

......以及 PHP 文件(标头部分)

$file = '/download/real-pdf-file.pdf'; //this is the physical file path
$filename = 'user-pdf-file.pdf'; //this is the file name user will get
header('Cache-Control: public, must-revalidate');
header('Pragma: no-cache');
header('Content-Type: application\pdf');
header('Content-Length: ' .(string)(filesize($file)) );
header('Content-Disposition: attachment; filename='.$filename.'');
header('Content-Transfer-Encoding: binary');
header('X-Accel-Redirect: '. $file);

文件是通过这样的URL调用的:
download.php?id=a2a9ca5bd4a84294b421fdd9ef5e438ded7fcb09

我尝试了这里的几个示例/解决方案,但目前没有一个有效。该文件很大(每个文件大小在250到400MB之间),每个用户可以下载4个文件。
使用PHP下载部分没有问题,只有nginx配置似乎不起作用。未检测到任何错误日志。

为了保险起见:/download/real-pdf-file.pdf 确实是你的文件的绝对路径吗?在你的 PHP 脚本中,filesize($file) 的输出是什么?如果你的 PHP 确实正确,那么你的 nginx 配置就有问题。你告诉它:“如果通过 X-Accel-Redirect 头请求 URI /download/real-pdf-file.pdf,则提供文件 /protected/read-pdf-file.pdf”。除非有你的文件的副本(或同名文件或链接),否则这是行不通的。 - Carsten
PHP将把它们输出为“/download/real-pdf-file.pdf”,但绝对路径是“/protected/read-pdf-file.pdf”。我尝试了几个配置,但在Chrome中出现“_ERR_FILE_NOT_FOUND”错误。 - Fadli Saad
2个回答

13

好的-这里有几个问题:

1) 将root放在location中是一个坏主意,根据nginx开发人员的说法。BAD IDEA

2) 用于告诉Nginx这是一个内部重定向的内部URL不应该向用户公开。

3) 我看不到您的download.php文件从哪里提供服务,因此我更改了您的root位置块以使用try_files,这样对/download.php的请求将由该文件而不是index.php提供服务。

您的项目应布置如下:

project\
    html - this is the root of your website
    protected  - this directory is not accessible directly

你的 Nginx 配置应该如下:

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;

        root   /path/to/project/html;

        location / {
            try_files $uri /index.php?$args;
        }

        location /protected_files {
            internal;
            alias /path/to/project/protected;
        }
    }
}

不要重复使用相同的名称表示不同的含义,这会非常令人困惑。我已经更改了它们,现在protected仅指实际物理目录,其中包含您要提供的文件。protected_files只是一个字符串,允许Nginx匹配来自x-accel头的请求。

您PHP代码中唯一需要更改的是使用正确的字符串以允许Nginx获取内部位置:

$aliasedFile = '/download/real-pdf-file.pdf'; //this is the nginx alias of the file path
$realFile = '/path/to/project/protected/real-pdf-file.pdf'; //this is the physical file path
$filename = 'user-pdf-file.pdf'; //this is the file name user will get
header('Cache-Control: public, must-revalidate');
header('Pragma: no-cache');
header('Content-Type: application\pdf');
header('Content-Length: ' .(string)(filesize($realFile)) );
header('Content-Disposition: attachment; filename='.$filename.'');
header('Content-Transfer-Encoding: binary');
header('X-Accel-Redirect: '. $aliasedFile);
exit(0);

你的解决方案似乎可行,但仍然卡在正确的语法上,以便nginx从Windows中挑选文件。Google Chrome 仍然返回 Error 6 (net::ERR_FILE_NOT_FOUND): The file or directory could not be found. 我们需要为下载设置重写吗,例如rewrite ^/download/(.*) /download.php?id=$1 last; - Fadli Saad
我会接受这个解决方案,因为在Windows中,我们需要使用正斜杠的完整文件路径,例如_C:\nginx\file.pdf_才能完全工作。 - Fadli Saad

3

根据Danack的建议和解决方案以及Carsten的澄清,我发现在Windows Server中,我们需要像这样在别名中设置完整路径:

location /protected_files {
            internal;
            alias C:/absolute/path/to/project/protected/;
        }

请注意需要额外的正斜线(在我的情况下,开发使用Windows 7 Pro,部署使用Windows Server 2008)。唯一的问题现在是我需要测试并发下载以查看服务器资源是否被占用。 我刚刚开始接触nginx,因为它似乎比Apache更快。感谢各位的启示!很高兴成为Stackoverflow社区的一员 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接