如何启用 X-Accel-Redirect

How to enable X-Accel-Redirect

我需要将 tmpfile() 在服务器端创建的文件发送到浏览器。

我正在使用两个不同的 Docker 容器:phpnginx(docker 组合)。

我的 nginx 配置文件(Docker 容器 nginx):

server {
    listen 80 default_server;
 
    index index.php index.html index.htm;
 
    root /app;

    location ~ [^/]\.php(/|$) {
        autoindex on;
        autoindex_exact_size on;
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
 
    error_log  stderr warn;
    access_log  /dev/stdout main;
}

我的PHP代码(Docker容器php):

<?php

$file = tmpfile();
fwrite($file, "hello world");

$metaData = stream_get_meta_data($file);
$fileName = $metaData['uri'];

header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Content-type: text/plain');
header('Content-Length: ' . filesize($fileName));
header('X-Sendfile: ' . $fileName);

fclose($file); 

但是浏览器只会得到一个空白页面,而不是下载弹出窗口。

好的,让我们从头开始。

  1. 很明显,nginx 需要直接访问将通过 X-Accel-Redirect header 提供的文件。这意味着您需要在 nginxphp 容器之间共享某种卷。通常这不是问题(例如,请参阅 How To Share Data between Docker Containers 文章)。但是 tmpfile() 将在临时目录(/tmp 或类似的目录)中创建一个临时文件,并使其成为容器之间的共享卷可能非常棘手。

  2. 即使您设法解决了前面的部分,在 fclose($file); 行之后临时文件将被关闭并删除。这就是为什么尝试同时使用 tmpfile()X-Accel-Redirect 是一个坏主意的主要原因。好吧,您可以尝试刷新输出缓冲区并等待一段时间,让 nginx 有机会在脚本关闭并删除文件之前读取文件,但我不确定它是否会起作用并且它不会似乎也是一个很好的解决方案。

你可以做什么?

首先,您根本不需要坚持 X-Accel-Redirect。当你想使用 nginx 服务已经存在的文件时,它可以给你显着的性能优势。但是您将首先创建该文件,使用 PHP 将一些数据写入磁盘并使用 nginx 从磁盘读取。您只需将该数据直接写入 STDOUT.

这种方法的缺点是您事先不知道数据量,也无法设置正确的 Content-Length header 通过 chunked-encoded 提供数据HTTP 流。如果你不想那样做,你可以使用输出缓冲 ob_... 函数。使用 ob_start(), then after all your data has been written to the buffer use ob_get_contents() to get its contents and ob_get_length() to get the data size and then close output buffer with the ob_end_clean() 开始缓冲。现在您可以在将数据发送到 STDOUT 之前正确设置 Content-Length header(如果出现问题,则 return 是正确的错误代码)。

作为最后的手段,如果您仍然想完全使用 tmpfile() 一个,您可以使用 [=33] 将其内容输出到 closing/deleting 之前的 STDOUT =]函数。