Is there any way to make asynchronous requests to a url through PHP?

2

I need to make several requests (about 200 requests) for an external url, but I do not want php to "wait" for the end of the request.

Remembering that I want to avoid using functions like exec and such things that run on the command line, because shared servers do not support such operations.

Is there any way to do asynchronous requests to a url through PHP?

    
asked by anonymous 24.03.2016 / 17:27

3 answers

5

The Guzzle library in its latest versions has support for asynchronous requests.

Example taken from the documentation.

$requests = [
    $client->createRequest('GET', 'http://httpbin.org'),
    $client->createRequest('DELETE', 'http://httpbin.org/delete'),
    $client->createRequest('PUT', 'http://httpbin.org/put', ['body' => 'test'])
];

$options = [];

// Cria pool. 
$pool = new Pool($client, $requests, $options);
//envia todas as requisições e quando elas terminarem chama o callback complete.
Pool::send($client, $requests, [
    'complete' => function (CompleteEvent $event) {
           echo 'Completed request to ' . $event->getRequest()->getUrl() . "\n";
           echo 'Response: ' . $event->getResponse()->getBody() . "\n\n";
     }
]);

//continue seu código aqui.

In this case all requests will be executed in parallel without blocking

    
24.03.2016 / 18:41
2

Just as a complement to the @ViniciusZaramella answer, where he cites the requests from Guzzle\Http .

Explanation on Guzzle's Asynchronism

As for the asynchronism of the Guzzle requests, I noticed that the same, when using the Pool class, actually sends several requests at once in background , thus having their responses asynchronously.

In case, if we made a request with Guzzle , normally, without using Pool , within a for 0 to 200, the request would wait for the response of the other to make a request.

Example:

  $guzzle = new GuzzleHttp\Client;

  for ($i = 0; $i  < 200; $i++)
  {
       $guzzle->get('http://httpbin.org/get', ['query' => ['i' => $i]]);
  }

In the case of Pool , the requests are sent "at once" - according to the configuration you set to concurrency , which is the number of requests made at a time in the queue.

$requests = function () {

    $guzzle = new GuzzleHttp\Client;

    for ($i = 0; $i  < 200; $i++)
    {
          yield $guzzle->get('http://httpbin.org/get', ['query' => ['i' => $i]]);
    }
};


$pool = new Pool($guzzle, $requests(), [
    'concurrency' => 50, // envia de 30 em 30,
    'rejected' => function ($exception, $index)
    {

    },

    'fulfilled' => function ($response, $index)
    {

    }
]);


$poll->promise()->wait();

I performed similar tests on both cases. The result was that, in the case similar to the first example, it took 45 to 50 seconds for a request (enough to give that PHP time-out error). In the second case, only 5 to 7 seconds were used at run time.

So, it really is an effective method.

Other Asynchronism

Before Vinicius's response was published, I had found on the internet to make asynchronous requests with PHP. However, by performing such an operation, PHP will not wait for the response , but will only make the request while the code will run normally until the end.

function async_request($method, $url, $params, array $headers = [])
{

    $method = strtoupper($method);

    $sendData = in_array($method, array('PUT', 'POST'));

    $data = http_build_query($params);

    $parts = parse_url($url) + [
        'port'  => 80,
        'path'  => '/',
    ];

    $headers += [
        'Content-Type'   => 'application/x-www-form-urlencoded',
        'Host'           => $parts['host'],
        'Content-Length' => !$sendData ? 0 : strlen($data),
        'Connection'     => 'Close'
    ];

    $path = !$sendData ? $parts['path'] . '?' . $data : $parts['path'];

    $output_header = sprintf('%s %s HTTP/1.1', $method, $path) . "\r\n";

    foreach ($headers as $name => $value)
    {
        foreach((array) $value as $v)
        {
            $output_header .= "$name: $v\r\n";
        }
    }

    $output_header .= "\r\n";

    $handle = @fsockopen($parts['host'], $parts['port'], $_errno, $_errstr, 30);

    if ($handle === false)
    {
        throw new \RuntimeException($_errstr);
    }

    fwrite($handle, $output_header);

    if ($sendData) fwrite($handle, $data);

    fclose($handle);

    return $output_header;

}

You can test this function as follows:

#index.php

assync_request('GET', 'http://localhost/target.php', []);

echo 'terminou';

#target.php

sleep(5);
file_put_contents('gato.txt', "Meow!!!\n\n", FILE_APPEND);

When you make the request, you will notice that the word 'terminou' is displayed immediately. And after 5 seconds, the gato.txt file will be created. This is because php ran a socket in backgroud, which was responsible for the target.php request.

This was the idea I had initially about asynchronism, but anyway the answer from @ ViniciusZaramella answered me better.

    
28.03.2016 / 18:04
-3

An old routine that I developed:

class Background
{

    /*
    $cmd -> A linha de comando a executar.
    $opt -> Opção de parâmetros para o ambiente onde executa o script.
    */
    public static function Call($cmd, $opt = 'start')
    {

        if (stripos(php_uname('s'), 'windows') !== false) {
            /*
            Condições de parâmetros para ambiente Windows.
            */
            switch ($opt) {
                default:
                case 'start':
                    $prefix = 'start /B '; // Esse aqui é o padrão, pois é compatível com as versões mais recentes do Windows.
                    $sufix = '';
                    break;
                case 'nul':
                    $prefix = '';
                    $sufix = ' > NUL 2> NUL';
                    break;
            }
        } else {
            /*
            Opções para ambiente *nix. (isso inclui os-x)
            Normalmente o sufixo ' &' é compatível com diversas distribuições Linux. Esse parâmetro diz ao sistema operacional executar em background.
            */
            switch ($opt) {
                default:
                case '&':
                    $prefix = '';
                    $sufix = ' &';
                    break;
                case 'dev-null':
                    $prefix = '';
                    $sufix = ' > /dev/null 2>/dev/null &';
                    break;
            }
        }

        exec(sprintf('%s%s%s', $prefix, $cmd, $sufix));

        return null;
    }

}

define('PHP_PATH', '/local/do/binario/php');

echo 'start '.microtime(true);
Background::Call(PHP_PATH.' "/local/de/um/arquivo.php"');
Background::Call(PHP_PATH.' "/local/de/um/arquivo.php"');
Background::Call(PHP_PATH.' "/local/de/um/arquivo.php"');
echo PHP_EOL.'end '.microtime(true);

file.php

<?php

/*
Fecha o output, ou seja, faz com que o script que invocou, não fique esperando por uma resposta.
*/
fclose(STDOUT);

/*
Daqui em diante, pode fazer o que bem entender.
Claro, esteja ciente de que tudo aqui está sendo executado em ambiente CLI (Command Line Interface).

Vamos fazer um exemplo para simular e testar se está funcionando.
*/

file_put_contents('background.txt', 'start '.microtime(true).PHP_EOL, FILE_APPEND);
sleep(5); // espera 5 segundos
file_put_contents(BASE_DIR.'background.txt', 'end '.microtime(true).PHP_EOL, FILE_APPEND);

About the asynchronous execution routine

There is no process control. Basically an execution command is sent by the exec() function, which, combined with some parameters, ignores the return of a response. By doing so, asynchronous executions.

Combinations vary by environment, so be aware that it's not enough just to copy the code and think it will work like magic.

In method Call() of class Background , just add new parameters if necessary.

Example:

            /*
            Opções para ambiente *nix. (isso inclui os-x)
            Normalmente o sufixo ' &' é compatível com diversas distribuições Linux. Esse parâmetro diz ao sistema operacional executar em background.
            */

            switch ($opt) {
                default:
                case '&':
                    $prefix = '';
                    $sufix = ' &';
                    break;
                case 'dev-null':
                    $prefix = '';
                    $sufix = ' > /dev/null 2>/dev/null &';
                    break;
                case 'dev-null2':
                    $prefix = '';
                    $sufix = ' /dev/null 2>&1 &';
                    break;
                case 'outro-nome-como-quiser':
                    $prefix = '';
                    $sufix = ' /algum/comando/diferente/que/funcione/num/ambiente/especifico';
                    break;
            } 

The script is simple, clean, easy to understand. It does not depend on third-party libraries.

    
24.03.2016 / 19:45