11

Outputting content as soon as PHP generates it is fine when using Apache with PHP as a module as you can simply disable output_buffering in PHP and use flush() or implicit_flush(1). This is what I previously used and it worked fine.

I'm running into an issue since having switched to PHP-FPM wherein I cannot get Apache (2.4) to output PHP's content until the entire script has completed. I still have output_buffering off and flushing in place but that's not enough. Apache isn't using mod_gzip (and that would have affected both the PHP module as well anyway).

Nginx has an option to disable proxy_buffering which, from reading other people's comments fixes this, but I cannot find any way of doing this in Apache.

Here's how PHP is currently being called within Apache:

<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>

The Apache documentation mentions flushpackets (used above) which appears to be what is needed, but then it also goes on to say that it only applies to AJS for now, not all proxied content so it won't do anything in this case.

Echoing enough whitespace to fill the buffer may work, but it's a messy workaround which is far from ideal.

In short: Does anyone know the correct way of having Apache send PHP content as soon as it's echo'd rather than waiting until script completion?

3
  • Have you looked at perhaps php_value output_buffering Off inside .htaccess or php.ini? In either case you could put this command inside a <files> or <VirtualHost> blocks if you didn't want to set it globally. Commented Nov 10, 2015 at 11:44
  • 1
    I mentioned in the first paragraph that output_buffering is already set to off (via php.ini so it's done globally). Commented Nov 10, 2015 at 14:35
  • Apologies, I'd misread that sentence. Commented Nov 10, 2015 at 14:50

5 Answers 5

21

I successfully disabled output buffering by rewriting your Proxy section (based on 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>
Sign up to request clarification or add additional context in comments.

4 Comments

Is this a bug then, as this seems like two different ways of writing the same thing and thus should perform identically.
You just solved for me something I've been scratching my head over for years, I've never come across the right way to flush the output over fcgi. You have no idea the lengths I've gone to to debug my scripts to get around that. I think this just added back 5 years to my life. I owe you a beer sir.
How do you make it work with PHP in a docker container, such as ProxyPassMatch "^(.+\.php)$" "fcgi://host.docker.internal:9998/home/rr/cashare/cashare_platform/$1"
This fixed my issue with SSE and laravel after 3 days searching any possible settings, thank you so much, is any problem having this globally enabled? I mean for my entire laravel app?
5

Reposting the answer I just posted to a very similar question here: How to disable buffering with apache2 and mod_proxy_fcgi?

A few notes, since I just spent the past few hours experimenting to find the answer to this question:

  1. It's not possible to entirely disable output buffering when using mod_proxy/mod_proxy_fcgi, however, you can still have responses streamed in chunks.
  2. It seems, based on my experimentation, that chunks have to be at least 4096 bytes before the output will be flushed to the browser.
  3. You can disable output buffering with the mod_fastcgi or mod_fcgi module, but those mods aren't as popular/widely used with Apache 2.4.
  4. If you have mod_deflate enabled and don't set SetEnv no-gzip 1 for the virtualhost/directory/etc. that's streaming data, then gzip will not allow the buffer to flush until the request is complete.

I was testing things out to see how to best use Drupal 8's new BigPipe functionality for streaming requests to the client, and I posted some more notes in this GitHub issue.

2 Comments

I just need to mention this post to complement: jeffgeerling.com/blog/2016/… It helped me a lot.
You can disable output buffering when using mod_proxy_fcgi and PHP-FPM. To do so you have to set flushpackets=on on ProxyPassMatch or Proxy, depending on your configuration. I disabled it by adding configuration with Proxy section from answer above to apache virtual hosts config.
3

In my environment (Apache 2.4, php-fpm) it worked when turning off compression and padding the output to output_buffering, see script:

header('Content-Encoding: none;');
$padSize = ini_get('output_buffering');

for($i=0;$i<10;$i++) {
  echo str_pad("$i<br>", $padSize);
  flush();
  sleep(1);
}

3 Comments

yeah, in my use case I don't like the idea of stuffing the buffer and I don't like changing/compromising settings for an entire application just for the sake of a couple of tasks.
just turn off output_buffering, even for a specific folder, via .user.ini or php.ini file
this is the only thing that worked for me, even though ini_get('output_buffering') returns zero and I just hardcoded 10K padding
2

https://www.php.net/manual/en/function.fastcgi-finish-request.php was what saved my sanity. I tried all kinds of hacks and techniques to get Apache and php-fpm (7.4) to display progress in a browser for a long-running process, including Server-Sent Events, writing progress to a text file and polling it with xhr, flush()ing like crazy, etc. Nothing worked until I did something like this (in my MVC action-controller)

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]);
}

and then some 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);

Voilà!

Comments

-3

A hack to make PHP FPM with Apache 2.4 mod_proxy work:

  • call ob_end_clean() at the beginning of your PHP script
  • call flush() at least 21 times to flush your output instead of calling it once; always send at least one character between calling flush()

using ob_end_clean() without ob_start() doesn't make sense to me, but it seems to help - and it returns true (=success!)

1 Comment

ob_end_clean() works because by there's output buffering configured in php.ini

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.