Using PHP and Server Sent Events (flushing with proxy_fcgi) - php

I use Ubuntu 17.04, Apache 2.4, proxy_fcgi, and php-fpm. Everything works and connects nicely, except for flushing for Server Sent Events.
Flushing used to work nicely with mod_fastcgi and fastcgiexternalserver with "-flush". Now with Ubuntu 17.04, it doesn't include mod_fastcgi, and proxy_fcgi is recommended.
With proxy_fcgi I've disabled gzip, output buffering, use "Content-Encoding: none", the only real way for connection_aborted and flush to work is if you send around 32K (I'm guessing this is because of proxy buffering?)
It says in the Apache Docs that you cannot set ProxyReceiveBufferSize or ProxyIOBufferSize less than 512.
There really should be an easier way to do this with proxy_fcgi.
Example code of sending data for Server Sent Events:
while (!connection_aborted() ) {
echo('data: {}' . PHP_EOL . PHP_EOL);
flush();
} // While //
Edit: I've tried ob_flush() too, but I disabled Output Buffering (ob_*) with ob_end_clean() previously, and ob_flush() will return an error.

Albeit this question has been asked some years ago, I just ran into a similar problem with Apache 2.4 and mod_fcgid. The PHP application did directly return data without buffering (tested with internal server php -S 0.0.0.0:8080 index.php) - but it was buffered when being used with Apache.
The following configuration disables output buffering for mod_fcgid (default size is 65536 bytes)
FcgidOutputBufferSize 0
https://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#FcgidOutputBufferSize

Related

PHP flush not working on centOS with Apache

I have a centOS 8 server with Apache FPM/FastCGI as webserver.
I want the output to be seen right after each echo.
I tried setting output_buffering = Off in php.ini and used below code:
ob_implicit_flush();
ob_start();
echo '1';
ob_end_flush();
#ob_flush();
flush();
sleep(5);
echo '2';
but page remains on loading for 5 seconds and outputs 12 instead of outputting 1 then 2 after 5 seconds.
I used fastcgi_finish_request() to output the whole buffer and do stuff in background and it works correctly.
And also, gzip and deflate are both turned off.
I use above code on my local Windows machine with Apache and also on another server with LiteSpeed and they work without a problem.
Your help is much appreciated!

Switch from php-mod to php-fpm Output buffering problem

When using php-mod and fastcgi the code executes perfectly and every second i get an output but switching to php-fpm the code lags a few seconds before outputting depending on output size
Tried the following and combinations of
setting output buffer 0 in php ini
ob_implicit_flush
ob_start
ob_end_flush
header Content-Encoding = none
implicit_flush 1
ob_end_clean
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
while( true ){
$time = date('r');
echo "retry:1000\r\n";
echo "data: ".$time;
echo "\r\n\r\n";
ob_flush();
flush();
sleep(1);
}
?>
This is for a production server and php-mod is not an option i also got it to work in Fastcgi with
FcgidOutputBufferSize 0
is there a way to make the code work on php-fpm so the output is send immediately as in php-mod and fastcgi ?
P.S Running : Ubuntu 18.04, Apache 2.4.29, PHP 7.2
After a few days i have discovered the only way to get this to work in php-fpm is to fill the output buffer. This is really inefficient ! Let me explain :
Say you are using Server-send events and your output buffer is 4096, you process every second even if you do not return anything you still send about 4Kb output to client where mod_php and fast-cgi sends only data when there is an output.
If anyone else has this problem this is my best solution : run main site on php-fpm ex. example.com and make a sub-domain ex. push.example.com and setup fast-cgi / php_mod[NOT RECOMMENDED PRODUCTION] on sub-domain now you can keep the connection open and process data without sending output to client.
PS. I saved Session variables in database so both domain and sub-domain can access it see https://github.com/dominicklee/PHP-MySQL-Sessions the other thing is to let sub-domain send CORS. in PHP add header('Access-Control-Allow-Origin: https://example.com');

Disabling Output Buffer with Apache and PHP-FPM via mod_proxy

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?
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>
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:
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.
It seems, based on my experimentation, that chunks have to be at least 4096 bytes before the output will be flushed to the browser.
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.
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.
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);
}
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Ă !
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!)

PHP mod_fcgi with fastcgi_finish_request();

I want to use the fastcgi_finish_request() function.
I have CPanel installed on my server and PHP and Apache are both configured through that. Since I cannot edit Apache or PHP configuration manually (because of CPanel), I used easyApache in WHM to build it in order to get fastcgi.
I saw an option caled Mod FCGID, so I enabled it.
After rebuilding PHP and Apache with that option enabled, I still get call to undefined function when calling the fastcgi_finish_request function.
A little late, but good info for people. In my experience working with PHP 5.5.7.
PHP using mod_php (standard Apache):
ob_start();
header("Connection: close\r\n");
header('Content-Encoding: none\r\n');
// your code here
$size = ob_get_length();
header("Content-Length: ". $size . "\r\n");
// send info immediately and close connection
ob_end_flush();
flush();
// run other process without the client attached.
For PHP using FastCGI and PHP_FPM:
// your code here
fastcgi_finish_request();
// run other process without the client attached.
Note that for us, after fastcgi_finish_request() was executed, log_error no longer worked. I assume it is because the connection to Apache is also severed and it cannot communicate with FastCGI to log the error.
fastcgi_finish_request is PHP-FPM SAPI specific function, unavailable in standard php-fcgi binary (used by Apache [mod_fcgid, mod_fastcgi], nginx, lighttpd etc).

PHP's apache_setenv function causes 500 Internal Server Error

apache_setenv ( 'no-gzip', 1 )
I'm trying to disable gzip for a certain page's output, but only that page. This works fine on testing servers, but not the production server, which is running the same thing (CentOS and Apache), works on Ubuntu though.
Anyway, do you know why? Or is there some other alternative?
I was thinking of using ob_start () to put all output in a buffer, and then unzip it myself with a PHP function then call ob_end_flush ()... or would it not be gzipped until right before Apache sends it to the client?
Thanks for any help.
Please verify that php is running as a module rather than a cgi-extension and safe mode must be disabled.

Categories