I'm willing to create a handler on ReactPHP that is able to return a "202 Accepted" immediately after the request has been made, but keeps doing things on the background.
For this example, no matter if it's 202 or its merely 200. The problem is to push back the heavy load to the loop and return before executing the load.
To test I created a blank dir and required ReactPhp via composer. Then I created a file named server.php with this:
<?php
declare( strict_types = 1 );
namespace App;
require __DIR__ . '/vendor/autoload.php';
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use React\EventLoop\Loop;
use React\Http\HttpServer;
use React\Http\Message\Response;
use React\Socket\SocketServer;
function respond( ServerRequestInterface $request ) : ResponseInterface
{
// This creates only a "humanized id" to play with multiple concurrent
// browsers and watching the matches between the echoes in the console
// and the results on the browser window
$path = $request->getUri()->getPath();
$callId = mt_rand( 1000, 9999 );
$id = json_encode( [ 'path' => $path, 'callId' => $callId ] );
// I simulate a heavy-load that lasts 10 seconds. I print the
// numbers 0 to 9 waiting 1 second between each other.
// I was expecting that pushing this into the Loop with a callback
// would not block the request, as what I do here (I think) is
// just call Loop::futureTick( [callback] ) to set the callback but
// not to execute it.
Loop::futureTick( function() use ( $id ) {
$first = true;
for( $i = 0; $i < 10; $i++ )
{
if( ! $first )
{
sleep( 1 );
}
echo( $i . ' - ' . $id . PHP_EOL );
$first = false;
}
} );
// I was expecting to be here *after* setting the callback
// and *before* executing the callback.
// Nevertheless the browser does not display the result
// until after 10 seconds, instead of immediately.
return Response::plaintext( 'Hello World! - ' . $id . PHP_EOL );
}
$server = new HttpServer( 'App\\respond' );
$url = '0.0.0.0:22900';
$socket = new SocketServer( $url );
$server->listen( $socket );
echo "Server running at http://" . $url . PHP_EOL;
When I run the server in the shell I see it running.
Essentially it creates a server, it sets the function respond as the handler and, overall, it works.
Except that I was expecting the return Response::plaintext(...) to be responded immediately because I thought that the callback inside Loop::futureTick() would be called after returning and therefore after the response was sent.
Instead, I see the loop counting from 0 to 9 during 10 seconds in the shell and only after that the browser displays the response.
Question
How can I detach the heavy load from the response hook and push it into the Loop so it's executed later, way after the response was sent to the browser?
I solved it by using the Async library from ReactPhp here https://reactphp.org/async/
First I required it as it seems it does not come with the default ReactPhp requirement, by doing this:
Then I changed 3 pieces of code:
async()call.await()call.sleep()by an asynchronous sleep from ReactPhp.I did all by importing the functions into the namespace so the code is easier to read than setting the full namespace there:
In the
usesection:The full handler:
Now the browser displays the response immediately, and way after the response has been printed on the browser the console shows the loops.
Even doing that in multiple browsers, each loop is run "in parallel" to the other.
This screenshot shows:
We can see IDs already printed in the browser window and still the CLI in the middle of the loop (second 6 for ID A and second 3 for ID B):