【问题标题】:Disabling Output Buffer with Apache and PHP-FPM via mod_proxy通过 mod_proxy 禁用 Apache 和 PHP-FPM 的输出缓冲区
【发布时间】:2016-02-11 06:24:50
【问题描述】:

在将 Apache 与 PHP 一起使用作为模块时,只要 PHP 生成内容就可以输出内容,因为您可以简单地在 PHP 中禁用 output_buffering 并使用 flush() 或implicit_flush(1)。这是我以前用过的,效果很好。

自从切换到 PHP-FPM 后,我遇到了一个问题,在整个脚本完成之前,我无法让 Apache (2.4) 输出 PHP 的内容。我仍然关闭了 output_buffering 并进行了刷新,但这还不够。 Apache 没有使用 mod_gzip(无论如何这也会影响 PHP 模块)。

Nginx 有一个禁用 proxy_buffering 的选项,通过读取其他人的 cmets 可以解决此问题,但我在 Apache 中找不到任何方法。

这是当前在 Apache 中调用 PHP 的方式:

<FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php-fpm/php-fpm.sock|fcgi://localhost/"
</FilesMatch>

<Proxy fcgi://localhost/ enablereuse=on retry=0 timeout=7200 max=500 flushpackets=on>
</Proxy>

Apache 文档提到了flushpackets(上面使用过),这似乎是需要的,但它还继续说它目前仅适用于AJS,并非所有代理内容,因此它不会在此执行任何操作案例。

回显足够的空白来填充缓冲区可能会起作用,但这是一个非常不理想的解决方法。

简而言之:有谁知道让 Apache 在回显后立即发送 PHP 内容而不是等到脚本完成的正确方法?

【问题讨论】:

  • 您是否在.htaccessphp.ini 中查看过php_value output_buffering Off?在任何一种情况下,如果您不想全局设置,都可以将此命令放在 &lt;files&gt;&lt;VirtualHost&gt; 块中。
  • 我在第一段中提到 output_buffering 已经设置为关闭(通过 php.ini 全局完成)。
  • 抱歉,我看错了那句话。

标签: php apache


【解决方案1】:

我通过重写您的Proxy 部分(基于this answer)成功禁用了输出缓冲:

<FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php-fpm/php-fpm.sock|fcgi://localhost"
</FilesMatch>

<Proxy fcgi://localhost>
    ProxySet enablereuse=on flushpackets=on
</Proxy>

【讨论】:

    【解决方案2】:

    在这里重新发布我刚刚发布的一个非常相似的问题的答案:How to disable buffering with apache2 and mod_proxy_fcgi?

    一些笔记,因为我刚刚花了几个小时来尝试找到这个问题的答案:

    1. 使用mod_proxy/mod_proxy_fcgi 时,无法完全禁用输出缓冲,但是,您仍然可以将响应分块流式传输。
    2. 根据我的实验,似乎在将输出刷新到浏览器之前,块必须至少为 4096 字节。
    3. 可以使用 mod_fastcgimod_fcgi 模块禁用输出缓冲,但这些模块在 Apache 2.4 中并不流行/广泛使用。
    4. 如果您启用了mod_deflate 并且没有为虚拟主机/目录/等设置SetEnv no-gzip 1。那是流数据,那么 gzip 将不允许缓冲区刷新,直到请求完成。

    我正在测试以了解如何最好地使用 Drupal 8 的新 BigPipe 功能来将请求流式传输到客户端,我在 this GitHub issue 中发布了更多注释。

    【讨论】:

    【解决方案3】:

    在我的环境(Apache 2.4,php-fpm)中,它在关闭压缩并将输出填充到output_buffering 时起作用,请参阅脚本:

    header('Content-Encoding: none;');
    $padSize = ini_get('output_buffering');
    
    for($i=0;$i<10;$i++) {
      echo str_pad("$i<br>", $padSize);
      flush();
      sleep(1);
    }
    

    【讨论】:

    • 是的,在我的用例中,我不喜欢填充缓冲区的想法,也不喜欢仅仅为了几个任务而更改/妥协整个应用程序的设置。
    • 只需通过 .user.ini 或 php.ini 文件关闭 output_buffering,即使对于特定文件夹也是如此
    【解决方案4】:

    https://www.php.net/manual/en/function.fastcgi-finish-request.php 挽救了我的理智。我尝试了各种黑客和技术来让 Apache 和 php-fpm (7.4) 在浏览器中显示长时间运行进程的进度,包括Server-Sent Events,将进度写入文本文件并使用 xhr 进行轮询,@987654323像疯了一样@ing,等等。直到我做了这样的事情(在我的 MVC 动作控制器中),没有任何效果

    public function longRunningProcessAction()
    {
    
        $path = \realpath('./data/progress.sqlite');
        $db = new \PDO("sqlite:$path");
        $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
        $stmt = $db->prepare("UPDATE progress SET status = :status");
        $stmt->execute([':status' => "starting"]);
        header("content-type: application/json");
        echo json_encode(['status'=>'started']);
        // this here is critical ...
        session_write_close();
        fastcgi_finish_request();
        // otherwise it will NOT work
        for ($i = 0; $i <= 150; $i++) {
            usleep(250*1000);
            $stmt->execute([':status' => "$i of 150"]);
            // this also works
            file_put_contents('./data/progress.txt',"$i of 150");
        }
        $stmt->execute([':status' => "done"]);        
    }
    // and...
    public function progressAction()
    {
        $path = \realpath('./data/progress.sqlite');
        $db = new \PDO("sqlite:$path");
        $query = 'SELECT status FROM progress';
        $stmt = $db->query($query);
        // and this is working as well..
        $text = file_get_contents('./data/progress.txt');
        return new JsonModel(['status'=>$stmt->fetchColumn(),'text'=>$text]);
    }
    

    然后是一些 Javascript (jQuery)

        var check_progress = function() {
            $.get("/my/job/progress").then(r=>{
            $("#progress").text(r.status);
            if (r.status === "done") { return; }
            window.setTimeout(check_progress,300);
        });
    
        $.post("/long/running/process",data).then(check_progress);
    

    瞧!

    【讨论】:

      【解决方案5】:

      使用 Apache 2.4 mod_proxy 使 PHP FPM 工作的技巧:

      • 在 PHP 脚本的开头调用 ob_end_clean()
      • 至少调用 flush() 21 次以刷新输出,而不是调用一次;在调用 flush() 之间总是发送至少一个字符

      在没有 ob_start() 的情况下使用 ob_end_clean() 对我来说没有意义,但它似乎有帮助 - 它返回 true(=成功!)

      【讨论】:

      • ob_end_clean() 有效,因为在 php.ini 中配置了输出缓冲
      猜你喜欢
      • 1970-01-01
      • 2013-02-08
      • 2012-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-11
      相关资源
      最近更新 更多