简体   繁体   中英

PHP: Send persistent asynchronous request

A long time ago I have built this function, which appeared OK at the time

public static function sendAsyncHTTPRequest($hostName, $port = 80, $method, $uri, $headers = array()){
    $fp = fsockopen($hostName, $port, $errno, $errstr, 30);
    if (!$fp) {
        throw new \Exception($errstr, $errno);
    } else {
        fwrite($fp, "$method $uri HTTP/1.1\r\n".
                    "Host: " . $hostName . "\r\n".
                    "Connection: Close\r\n".
                    join("\r\n", $headers)."\r\n\r\n");
        fclose($fp);
    }
}

Which the sole purpose of is to trigger some script from a client request, without slowing the request itself down, and without expecting a response. However I tried to use that function today, to start a websocket server and surprisingly found out it isn't asynchronous at all. Here's the piece of code that is supposed to start the server

\MF\System::sendAsyncHTTPRequest(SITE_DOMAIN, 80, 'GET', '/battleWS/startServer/'.$battleId);
header('Location: '.SITE_URL.'/battleWS/field/'.$battleId);

As you can see, I'm starting the server and then immediately redirecting the client to the page which connects to the server. Apparently when the client gets redirected the server script stops executing which is unexpected for me since I believed that I was sending an asynchronous request. I can confirm this because if I put a sleep in-between those two lines I start seeing the server's auto-shutdown countdown in the log file. I tried switching from fsockopen to stream_socket_client with no luck. Also this is the beginning of the server-starting script ( Called with sendAsyncHTTPRequest() )

set_time_limit(0);
ignore_user_abort(true);

Which is confusing me even more, since ìgnore_user_abort is supposed to keep the script executing.

I'm looking for a way to keep that server running after redirecting the client from the original request, without using libraries and frameworks.

You can execute a command-line PHP client in background to do the HTTP job. If it executed in background, it is asynchronous.

Example:

process.php This PHP script is run using PHP Cli.

<?php

if (!isset($argv[1]))
{
   die("Arguments not given\n");
}

$args = json_decode($argv[1]);
list($hostName, $port, $method, $uri, $headers) = $args;
sendAsyncHTTPRequest($hostName, $port, $method, $uri, $headers);


function sendAsyncHTTPRequest($hostName, $port, $method, $uri, $headers = array ())
{
   $fp = fsockopen($hostName, $port, $errno, $errstr, 30);
   if (!$fp)
   {
      // as your code is asynchronous, no way to catch this exception
      throw new \Exception($errstr, $errno);
   }
   else
   {
      fwrite($fp,
         "$method $uri HTTP/1.1\r\n" .
         "Host: " . $hostName . "\r\n" .
         "Connection: Close\r\n" .
         join("\r\n", $headers) . "\r\n\r\n");
      fclose($fp);
   }
}

execute.php This code is executed by apache (where you currently execute the sendAsyncHTTPRequest method).

$escaped_script = escapeshellarg(__DIR__ . '/process.php');
$escaped_args = escapeshellarg(json_encode(array($hostName, $port, $method, $uri, $headers)));
exec("/usr/bin/php {$escaped_script} {$escaped_args} > /dev/null 2>&1 & echo -n \$!");

Some details:

> /dev/null

will redirect standard output (ie. your echo, print etc) to a virtual file (all outputs written in it are lost).

2>&1

will redirect error output to standard output, writting in the same virtual and non-existing file. This avoids having logs into your apache2/error.log for example.

&

is the most important thing in your case : it will detach your execution of $command : so exec() will immediatly release your php code execution and create the expected asynchronous behaviour.

echo -n \$!

will give PID of your detached execution as response : it will be returned by exec() and makes you able to work with it (such as, put this pid into a database and kill it after some time to avoid zombies).

I believe that you will be able to do what you want using ReactPHP

https://github.com/reactphp/react

For example, something like this:

<?php

$i = 0;

$app = function ($request, $response) use (&$i) {
    $i++;

    $text = "This is request number $i.\n";
    $headers = array('Location:' => 'http://domain.com');

    $response->writeHead(200, $headers);
    $response->end($text);
};

$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$http = new React\Http\Server($socket);

$http->on('request', $app);

$socket->listen(1337);

$loop->run();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM