Reputation: 10725
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.
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?
Upvotes: 0
Views: 141
Reputation: 10725
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:
composer require react/async
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 use
section:
use function React\Async\async;
use function React\Async\await;
use function React\Promise\Timer\sleep;
The full handler:
function respond( ServerRequestInterface $request ) : ResponseInterface
{
$path = $request->getUri()->getPath();
$callId = mt_rand( 1000, 9999 );
$id = json_encode( [ 'path' => $path, 'callId' => $callId ] );
// CHANGE 1: Note the async() here wrapping all the callback
Loop::futureTick( async( function() use ( $id ) {
$first = true;
for( $i = 0; $i < 10; $i++ )
{
if( ! $first )
{
// CHANGE 2: Note the await() wrapping the sleep().
// CHANGE 3: The sleep() has been replaced thanks to the use statement.
await( sleep( 1 ) );
}
echo( $i . ' - ' . $id . PHP_EOL );
$first = false;
}
} ) );
// Added this to check in the CLI when the response is sent.
echo( 'About to respond! - ' . $id . PHP_EOL );
return Response::plaintext( 'Hello World! - ' . $id . PHP_EOL );
}
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):
Upvotes: 0