hollodotme / fast-cgi-client

A PHP fast CGI client for sending requests (a)synchronously to PHP-FPM
Other
551 stars 34 forks source link

Dump response as it goes back #11

Closed dugwood closed 7 years ago

dugwood commented 7 years ago

I didn't find a good title, but an example will be better. I've written this article based on https://github.com/adoy/PHP-FastCGI-Client/ (that you've used to build this very project): https://www.dugwood.com/949904-php5-opcode-caching-and-memory-storage-with-apc-xcache-in-command-line-interface-cli-or-cron.html

The important part here is the client call: $response = $client->request($request, null, create_function('$response', 'echo $response;'));

Here, every time the client receives a packet, it will send it to the function (here it's a simple echo()). It may be strange, as most of the people just want the answer from a PHP script, but I use it to access APCu's cache, and even OpCache for performance. So I run a lot of batch scripts with it, with took a long time to process, and I want to see the result as soon as the main PHP script echo() something.

I could write to a log, and on the main call read this log, but that's really not efficient I think. The best way to handle this would be to fire readResponse() (https://github.com/hollodotme/fast-cgi-client/blob/master/src/Client.php#L115) each time a packet arrives.

I use a function named show() to force the PHP-FPM to send the message I want:

class FastCGI_Controller
{
    private static $buffering = 4096;

    static public function init()
    {
        self::$buffering = (int) ini_get('output_buffering');
    }

    static public function show($string)
    {
        echo str_repeat("\033[0m", max(1, ceil((self::$buffering - strlen($string) % self::$buffering) / 4))).$string;
        flush();
    }
}

So in the main PHP script (called within PHP-FPM), I'll send some FastCGI_Controller::show('task 1: completed'.PHP_EOL);, and my PHP caller script will dump it immediately in my console (or cron log).

Maybe it's better to handle this another way, but so far I didn't think it through. Perhaps you'll have another idea to make this work.

Of course I can code it, and send a PR when done, but I need your input first :-)

hollodotme commented 7 years ago

@dugwood Thanks for the issue and the interesting use-case.

My first idea is to add another callback type to all request classes in AbstractRequest like for the other callbacks:

public function addPassThroughCallbacks(callable ...$callbacks) : void
public function getPassThroughCallbacks() : array

Then pass these callbacks to the relevant socket instance and call them here.

Please note: It's a first idea and I don't know yet if this could work.

I would really appreciate a [WIP] PR with an integration test that I can review, because I am not able to work on this for at least 2 weeks, because of conferences I attend or need to prepare for.

dugwood commented 7 years ago

Thanks @hollodotme, I'll try an integration like this. I've ended up on the same line of code, after browsing a little bit of your code.

Thanks for the suggestions, greatly appreciated. The log idea would have been good, for example in memory, but the fact that the request can be on another server would prevent this to work in some cases (and now I think I'll open ports on my other servers to request directly the php-fpm on the other server!).

Don't worry for the delay, after all I've been using a patched version of Aloy since 2014... and I don't really like the way I did it, so in your project I have to do better :-)

hollodotme commented 7 years ago

Great @dugwood! I think we'll figure out a good way to implement this together.

hollodotme commented 7 years ago

@dugwood: Your changes already made it to 1.x-dev. I fixed some little things until I got a bit distracted by that thread: https://twitter.com/hollodotme/status/912415517859811333 😂

Hope to bring it to 1.x-stable tomorrow and then patch development/master and bump the releases.

Thanks for your great work!

dugwood commented 7 years ago

@hollodotme it reminds me of some memcached's options! As integer in libmemcached, but as boolean in PHP...

The show() function (in README.md) was not supposed to use a sleep() call, it's really a solution for dumping data. Maybe one day I'll make it part of your package, as it's really useful (for batch scripts and passthrough), else echo() isn't print if under output_buffering. Or maybe I can try to set output_buffering to 1 :-)

Can't wait for the stable release! Will it make it's way to packagist too?

dugwood commented 7 years ago

Oh, I should just take packagist «1.x-dev» for now :-) And switch to stable when ready. Great!

hollodotme commented 7 years ago

Please don't close an issue before it is really solved. ;) I'll close it as soon as the new releases are out.

Regarding the example on README: I just wanted to simplify the example for better understanding by people with other use-cases. If it is a batch script or not doesn't really matter for the pass through callback demo.

The responsibility of the whole output part belongs to user's implementation, so there is no reason to add something for output_buffering/flushing to the lib. This only would add unwanted/hidden side-effects.

hollodotme commented 7 years ago

Version 1.4.0 is out for PHP >= 7.0!

hollodotme commented 7 years ago

Version 2.4.0 is out for PHP >= 7.1!

dugwood commented 7 years ago

Just installed on my server :-) Thanks for such short release update.

Maybe I'll add another PR for another issue I faced: when requesting PHP-FPM status (usually with /status?full), I had some trouble getting it to work. Hopefully I had my old version with Adoy's script, and checked against it until I made it work with yours. The missing part is:

$url = parse_url('/status?full');
$request = new GetRequest($url['path'], '');
$request->setCustomVar('QUERY_STRING', $url['query']);
$request->setCustomVar('SCRIPT_NAME', '/'.basename($url['path']));

As you can see, I need to set 2 custom vars. I don't know why the query-string was needed, but maybe I'll look into it one day :-) I see that almost all your example requests are POST, so I switch to it (useful for large data too), but for this very one I didn't have a choice.