web-dev-qa-db-fra.com

Comment faire des requêtes HTTP asynchrones dans PHP

Existe-t-il un moyen dans PHP de faire des appels HTTP asynchrones? Je ne me soucie pas de la réponse, je veux juste faire quelque chose comme file_get_contents(), mais pas attendre que la requête se termine avant d'exécuter le reste de mon code. Ce serait très utile pour déclencher des "événements" dans mon application ou pour déclencher de longs processus.

Des idées?

195
Brent

La réponse que j'avais acceptée auparavant ne fonctionnait pas. Il attendait toujours les réponses. Cela fonctionne cependant, tiré de Comment puis-je faire une requête GET asynchrone en PHP?

function post_without_wait($url, $params)
{
    foreach ($params as $key => &$val) {
      if (is_array($val)) $val = implode(',', $val);
        $post_params[] = $key.'='.urlencode($val);
    }
    $post_string = implode('&', $post_params);

    $parts=parse_url($url);

    $fp = fsockopen($parts['Host'],
        isset($parts['port'])?$parts['port']:80,
        $errno, $errstr, 30);

    $out = "POST ".$parts['path']." HTTP/1.1\r\n";
    $out.= "Host: ".$parts['Host']."\r\n";
    $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
    $out.= "Content-Length: ".strlen($post_string)."\r\n";
    $out.= "Connection: Close\r\n\r\n";
    if (isset($post_string)) $out.= $post_string;

    fwrite($fp, $out);
    fclose($fp);
}
42
Brent

Si vous contrôlez la cible que vous souhaitez appeler de manière asynchrone (par exemple, votre propre "longtask.php"), vous pouvez fermer la connexion à partir de cette extrémité et les deux scripts seront exécutés en parallèle. Cela fonctionne comme ceci:

  1. quick.php ouvre longtask.php via cURL (pas de magie ici)
  2. longtask.php ferme la connexion et continue (magie!)
  3. cURL retourne à quick.php lorsque la connexion est fermée
  4. Les deux tâches continuent en parallèle

J'ai essayé cela et ça fonctionne très bien. Mais quick.php ne saura rien de la durée de longtask.php, à moins que vous ne créiez un moyen de communication entre les processus.

Essayez ce code dans longtask.php, avant de faire autre chose. Il fermera la connexion mais continuera à fonctionner (et supprimera toute sortie):

while(ob_get_level()) ob_end_clean();
header('Connection: close');
ignore_user_abort();
ob_start();
echo('Connection Closed');
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush();
flush();

Le code est copié à partir de = Notes du contributeur du manuel PHP et légèrement amélioré.

26
Christian Davén

cela nécessite php5, je l'ai volé sur docs.php.net et édité à la fin.

Je l'utilise pour surveiller lorsqu'une erreur survient sur un site client, il m'envoie des données sans bloquer la sortie.

function do_post_request($url, $data, $optional_headers = null,$getresponse = false) {
    $params = array(
        'http' => array(
            'method' => 'POST',
            'content' => $data
        )
    );
    if ($optional_headers !== null) {
         $params['http']['header'] = $optional_headers;
    }
    $ctx = stream_context_create($params);
    $fp = @fopen($url, 'rb', false, $ctx);

    if (!$fp) {
        return false;
    }

    if ($getresponse) {
        $response = stream_get_contents($fp);
        return $response;
    }
    return true;
}
25
Bruce Aldridge

Vous pouvez faire de la supercherie en utilisant exec () pour appeler quelque chose qui peut faire des requêtes HTTP, comme wget, mais vous devez diriger toute la sortie du programme vers quelque part, comme un fichier ou/dev/null, sinon le PHP processus attendra cette sortie.

Si vous voulez séparer complètement le processus du fil de discussion Apache, essayez quelque chose du genre (je n'en suis pas sûr, mais j'espère que vous avez compris):

exec('bash -c "wget -O (url goes here) > /dev/null 2>&1 &"');

Ce n'est pas une bonne affaire, et vous voudrez probablement quelque chose comme un travail cron appelant un script de pulsation qui interroge une file d'attente d'événements de base de données réelle pour faire de vrais événements asynchrones.

15
Internet Friend

Depuis 2018, Guzzle est devenue la bibliothèque standard de facto pour les requêtes HTTP, utilisée dans plusieurs frameworks modernes. Il est écrit en pure PHP et ne nécessite l'installation d'aucune extension personnalisée.

Il peut très bien faire des appels HTTP asynchrones, et même les regrouper comme lorsque vous devez faire 100 appels HTTP, mais que vous ne voulez pas en exécuter plus de 5 à la fois.

Exemple de demande simultanée

use GuzzleHttp\Client;
use GuzzleHttp\Promise;

$client = new Client(['base_uri' => 'http://httpbin.org/']);

// Initiate each request but do not block
$promises = [
    'image' => $client->getAsync('/image'),
    'png'   => $client->getAsync('/image/png'),
    'jpeg'  => $client->getAsync('/image/jpeg'),
    'webp'  => $client->getAsync('/image/webp')
];

// Wait on all of the requests to complete. Throws a ConnectException
// if any of the requests fail
$results = Promise\unwrap($promises);

// Wait for the requests to complete, even if some of them fail
$results = Promise\settle($promises)->wait();

// You can access each result using the key provided to the unwrap
// function.
echo $results['image']['value']->getHeader('Content-Length')[0]
echo $results['png']['value']->getHeader('Content-Length')[0]

Voir http://docs.guzzlephp.org/fr/stable/quickstart.html#concurrent-requests

8
Simon East
/**
 * Asynchronously execute/include a PHP file. Does not record the output of the file anywhere. 
 *
 * @param string $filename              file to execute, relative to calling script
 * @param string $options               (optional) arguments to pass to file via the command line
 */ 
function asyncInclude($filename, $options = '') {
    exec("/path/to/php -f {$filename} {$options} >> /dev/null &");
}
8
philfreo
  1. Faux avortement de requête en utilisant CURL en réglant une valeur basse CURLOPT_TIMEOUT_MS

  2. réglez ignore_user_abort(true) pour continuer le traitement après la fermeture de la connexion.

Avec cette méthode, il n'est pas nécessaire d'implémenter la gestion de la connexion via des en-têtes et un tampon trop dépendant du système d'exploitation, du navigateur et de la version PHP

Processus maître

function async_curl($background_process=''){

    //-------------get curl contents----------------

    $ch = curl_init($background_process);
    curl_setopt_array($ch, array(
        CURLOPT_HEADER => 0,
        CURLOPT_RETURNTRANSFER =>true,
        CURLOPT_NOSIGNAL => 1, //to timeout immediately if the value is < 1000 ms
        CURLOPT_TIMEOUT_MS => 50, //The maximum number of mseconds to allow cURL functions to execute
        CURLOPT_VERBOSE => 1,
        CURLOPT_HEADER => 1
    ));
    $out = curl_exec($ch);

    //-------------parse curl contents----------------

    //$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    //$header = substr($out, 0, $header_size);
    //$body = substr($out, $header_size);

    curl_close($ch);

    return true;
}

async_curl('http://example.com/background_process_1.php');

Processus d'arrière-plan

ignore_user_abort(true);

//do something...

NB

Si vous souhaitez que cURL expire en moins d'une seconde, vous pouvez utiliser CURLOPTTIMEOUT_MS, bien qu'il existe un bogue/"fonctionnalité" sur les systèmes de type "Unix" provoquant l'expiration immédiate de libcurl si la valeur est <1000 ms avec l'erreur " Erreur cURL (28): le délai a été atteint ". L'explication de ce comportement est la suivante:

[...]

La solution consiste à désactiver les signaux à l'aide de CURLOPT_NOSIGNAL

Ressources

6
RafaSashi

Vous pouvez utiliser cette bibliothèque: https://github.com/stil/curl-easy

C'est assez simple alors:

<?php
$request = new cURL\Request('http://yahoo.com/');
$request->getOptions()->set(CURLOPT_RETURNTRANSFER, true);

// Specify function to be called when your request is complete
$request->addListener('complete', function (cURL\Event $event) {
    $response = $event->response;
    $httpCode = $response->getInfo(CURLINFO_HTTP_CODE);
    $html = $response->getContent();
    echo "\nDone.\n";
});

// Loop below will run as long as request is processed
$timeStart = microtime(true);
while ($request->socketPerform()) {
    printf("Running time: %dms    \r", (microtime(true) - $timeStart)*1000);
    // Here you can do anything else, while your request is in progress
}

Ci-dessous, vous pouvez voir la sortie de la console de l'exemple ci-dessus. Il affichera une horloge en direct simple indiquant combien de temps la demande est en cours d'exécution:


animation

6
stil

Vous pouvez utiliser des sockets non-bloquants et une des extensions pecl pour PHP:

Vous pouvez utiliser la bibliothèque qui vous donne une couche d'abstraction entre votre code et une extension pecl: https://github.com/reactphp/event-loop

Vous pouvez également utiliser un client http asynchrone, basé sur la bibliothèque précédente: https://github.com/reactphp/http-client

Voir les autres bibliothèques de ReactPHP: http://reactphp.org

Soyez prudent avec un modèle asynchrone. Je recommande de voir cette vidéo sur youtube: http://www.youtube.com/watch?v=MWNcItWuKpI

4
Roman Shamritskiy

laisse moi te montrer mon chemin :)

besoin de nodejs installé sur le serveur

(mon serveur envoie 1 000 https, la requête d'obtention ne prend que 2 secondes)

url.php:

<?
$urls = array_fill(0, 100, 'http://google.com/blank.html');

function execinbackground($cmd) { 
    if (substr(php_uname(), 0, 7) == "Windows"){ 
        pclose(popen("start /B ". $cmd, "r"));  
    } 
    else { 
        exec($cmd . " > /dev/null &");   
    } 
} 
fwite(fopen("urls.txt","w"),implode("\n",$urls);
execinbackground("nodejs urlscript.js urls.txt");
// { do your work while get requests being executed.. }
?>

urlscript.js>

var https = require('https');
var url = require('url');
var http = require('http');
var fs = require('fs');
var dosya = process.argv[2];
var logdosya = 'log.txt';
var count=0;
http.globalAgent.maxSockets = 300;
https.globalAgent.maxSockets = 300;

setTimeout(timeout,100000); // maximum execution time (in ms)

function trim(string) {
    return string.replace(/^\s*|\s*$/g, '')
}

fs.readFile(process.argv[2], 'utf8', function (err, data) {
    if (err) {
        throw err;
    }
    parcala(data);
});

function parcala(data) {
    var data = data.split("\n");
    count=''+data.length+'-'+data[1];
    data.forEach(function (d) {
        req(trim(d));
    });
    /*
    fs.unlink(dosya, function d() {
        console.log('<%s> file deleted', dosya);
    });
    */
}


function req(link) {
    var linkinfo = url.parse(link);
    if (linkinfo.protocol == 'https:') {
        var options = {
        Host: linkinfo.Host,
        port: 443,
        path: linkinfo.path,
        method: 'GET'
    };
https.get(options, function(res) {res.on('data', function(d) {});}).on('error', function(e) {console.error(e);});
    } else {
    var options = {
        Host: linkinfo.Host,
        port: 80,
        path: linkinfo.path,
        method: 'GET'
    };        
http.get(options, function(res) {res.on('data', function(d) {});}).on('error', function(e) {console.error(e);});
    }
}


process.on('exit', onExit);

function onExit() {
    log();
}

function timeout()
{
console.log("i am too far gone");process.exit();
}

function log() 
{
    var fd = fs.openSync(logdosya, 'a+');
    fs.writeSync(fd, dosya + '-'+count+'\n');
    fs.closeSync(fd);
}
4
user1031143
class async_file_get_contents extends Thread{
    public $ret;
    public $url;
    public $finished;
        public function __construct($url) {
        $this->finished=false;
        $this->url=$url;
    }
        public function run() {
        $this->ret=file_get_contents($this->url);
        $this->finished=true;
    }
}
$afgc=new async_file_get_contents("http://example.org/file.ext");
3
hanshenrik

L'extension swoole. https://github.com/matyhtf/swoole Infrastructure réseau asynchrone et simultanée pour PHP.

$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);

$client->on("connect", function($cli) {
    $cli->send("hello world\n");
});

$client->on("receive", function($cli, $data){
    echo "Receive: $data\n";
});

$client->on("error", function($cli){
    echo "connect fail\n";
});

$client->on("close", function($cli){
    echo "close\n";
});

$client->connect('127.0.0.1', 9501, 0.5);
3
Tony

Extension d'événement

Event l'extension est très appropriée. C'est une bibliothèque Libevent conçue pour les E/S événementielles, principalement pour la mise en réseau.

J'ai écrit un exemple de client HTTP qui permet de planifier un certain nombre de requêtes HTTP et de les exécuter de manière asynchrone.

Ceci est un exemple de classe de client HTTP basée sur l'extension Event .

La classe permet de planifier un certain nombre de requêtes HTTP, puis de les exécuter de manière asynchrone.

http-client.php

<?php
class MyHttpClient {
  /// @var EventBase
  protected $base;
  /// @var array Instances of EventHttpConnection
  protected $connections = [];

  public function __construct() {
    $this->base = new EventBase();
  }

  /**
   * Dispatches all pending requests (events)
   *
   * @return void
   */
  public function run() {
    $this->base->dispatch();
  }

  public function __destruct() {
    // Destroy connection objects explicitly, don't wait for GC.
    // Otherwise, EventBase may be free'd earlier.
    $this->connections = null;
  }

  /**
   * @brief Adds a pending HTTP request
   *
   * @param string $address Hostname, or IP
   * @param int $port Port number
   * @param array $headers Extra HTTP headers
   * @param int $cmd A EventHttpRequest::CMD_* constant
   * @param string $resource HTTP request resource, e.g. '/page?a=b&c=d'
   *
   * @return EventHttpRequest|false
   */
  public function addRequest($address, $port, array $headers,
    $cmd = EventHttpRequest::CMD_GET, $resource = '/')
  {
    $conn = new EventHttpConnection($this->base, null, $address, $port);
    $conn->setTimeout(5);

    $req = new EventHttpRequest([$this, '_requestHandler'], $this->base);

    foreach ($headers as $k => $v) {
      $req->addHeader($k, $v, EventHttpRequest::OUTPUT_HEADER);
    }
    $req->addHeader('Host', $address, EventHttpRequest::OUTPUT_HEADER);
    $req->addHeader('Connection', 'close', EventHttpRequest::OUTPUT_HEADER);
    if ($conn->makeRequest($req, $cmd, $resource)) {
      $this->connections []= $conn;
      return $req;
    }

    return false;
  }


  /**
   * @brief Handles an HTTP request
   *
   * @param EventHttpRequest $req
   * @param mixed $unused
   *
   * @return void
   */
  public function _requestHandler($req, $unused) {
    if (is_null($req)) {
      echo "Timed out\n";
    } else {
      $response_code = $req->getResponseCode();

      if ($response_code == 0) {
        echo "Connection refused\n";
      } elseif ($response_code != 200) {
        echo "Unexpected response: $response_code\n";
      } else {
        echo "Success: $response_code\n";
        $buf = $req->getInputBuffer();
        echo "Body:\n";
        while ($s = $buf->readLine(EventBuffer::EOL_ANY)) {
          echo $s, PHP_EOL;
        }
      }
    }
  }
}


$address = "my-Host.local";
$port = 80;
$headers = [ 'User-Agent' => 'My-User-Agent/1.0', ];

$client = new MyHttpClient();

// Add pending requests
for ($i = 0; $i < 10; $i++) {
  $client->addRequest($address, $port, $headers,
    EventHttpRequest::CMD_GET, '/test.php?a=' . $i);
}

// Dispatch pending requests
$client->run();

test.php

Ceci est un exemple de script côté serveur.

<?php
echo 'GET: ', var_export($_GET, true), PHP_EOL;
echo 'User-Agent: ', $_SERVER['HTTP_USER_AGENT'] ?? '(none)', PHP_EOL;

Usage

php http-client.php

Exemple de sortie

Success: 200
Body:
GET: array (
  'a' => '1',
)
User-Agent: My-User-Agent/1.0
Success: 200
Body:
GET: array (
  'a' => '0',
)
User-Agent: My-User-Agent/1.0
Success: 200
Body:
GET: array (
  'a' => '3',
)
...

(Découpé.)

Notez que le code est conçu pour un traitement à long terme dans le CLI SAPI .


Pour les protocoles personnalisés, envisagez d’utiliser une API de bas niveau, c’est-à-dire événements de tampon , tampons . Pour les communications SSL/TLS, je recommanderais l'API de bas niveau en conjonction avec l'événement contexte ssl . Exemples:


Bien que l'API HTTP de Libevent soit simple, elle n'est pas aussi flexible que les événements de tampon. Par exemple, l'API HTTP ne prend actuellement pas en charge les méthodes HTTP personnalisées. Mais il est possible d'implémenter pratiquement n'importe quel protocole en utilisant l'API de bas niveau.

Extension Ev

J'ai également écrit un exemple d'un autre client HTTP utilisant Ev extension avec sockets in mode non bloquant . Le code est légèrement plus détaillé que l'exemple basé sur Event, car Ev est une boucle d'événement à usage général. Il ne fournit pas de fonctions spécifiques au réseau, mais son observateur EvIo est capable d'écouter un descripteur de fichier encapsulé dans la ressource socket, en particulier.

Ceci est un exemple de client HTTP basé sur l'extension Ev .

L'extension ev implémente une boucle d'événement générale simple mais puissante. Il ne fournit pas d'observateurs spécifiques au réseau, mais son observateur d'E/S peut être utilisé pour le traitement asynchrone de sockets .

Le code suivant montre comment les requêtes HTTP peuvent être planifiées pour un traitement en parallèle.

http-client.php

<?php
class MyHttpRequest {
  /// @var MyHttpClient
  private $http_client;
  /// @var string
  private $address;
  /// @var string HTTP resource such as /page?get=param
  private $resource;
  /// @var string HTTP method such as GET, POST etc.
  private $method;
  /// @var int
  private $service_port;
  /// @var resource Socket
  private $socket;
  /// @var double Connection timeout in seconds.
  private $timeout = 10.;
  /// @var int Chunk size in bytes for socket_recv()
  private $chunk_size = 20;
  /// @var EvTimer
  private $timeout_watcher;
  /// @var EvIo
  private $write_watcher;
  /// @var EvIo
  private $read_watcher;
  /// @var EvTimer
  private $conn_watcher;
  /// @var string buffer for incoming data
  private $buffer;
  /// @var array errors reported by sockets extension in non-blocking mode.
  private static $e_nonblocking = [
    11, // EAGAIN or EWOULDBLOCK
    115, // EINPROGRESS
  ];

  /**
   * @param MyHttpClient $client
   * @param string $Host Hostname, e.g. google.co.uk
   * @param string $resource HTTP resource, e.g. /page?a=b&c=d
   * @param string $method HTTP method: GET, HEAD, POST, PUT etc.
   * @throws RuntimeException
   */
  public function __construct(MyHttpClient $client, $Host, $resource, $method) {
    $this->http_client = $client;
    $this->Host        = $Host;
    $this->resource    = $resource;
    $this->method      = $method;

    // Get the port for the WWW service
    $this->service_port = getservbyname('www', 'tcp');

    // Get the IP address for the target Host
    $this->address = gethostbyname($this->Host);

    // Create a TCP/IP socket
    $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    if (!$this->socket) {
      throw new RuntimeException("socket_create() failed: reason: " .
        socket_strerror(socket_last_error()));
    }

    // Set O_NONBLOCK flag
    socket_set_nonblock($this->socket);

    $this->conn_watcher = $this->http_client->getLoop()
      ->timer(0, 0., [$this, 'connect']);
  }

  public function __destruct() {
    $this->close();
  }

  private function freeWatcher(&$w) {
    if ($w) {
      $w->stop();
      $w = null;
    }
  }

  /**
   * Deallocates all resources of the request
   */
  private function close() {
    if ($this->socket) {
      socket_close($this->socket);
      $this->socket = null;
    }

    $this->freeWatcher($this->timeout_watcher);
    $this->freeWatcher($this->read_watcher);
    $this->freeWatcher($this->write_watcher);
    $this->freeWatcher($this->conn_watcher);
  }

  /**
   * Initializes a connection on socket
   * @return bool
   */
  public function connect() {
    $loop = $this->http_client->getLoop();

    $this->timeout_watcher = $loop->timer($this->timeout, 0., [$this, '_onTimeout']);
    $this->write_watcher = $loop->io($this->socket, Ev::WRITE, [$this, '_onWritable']);

    return socket_connect($this->socket, $this->address, $this->service_port);
  }

  /**
   * Callback for timeout (EvTimer) watcher
   */
  public function _onTimeout(EvTimer $w) {
    $w->stop();
    $this->close();
  }

  /**
   * Callback which is called when the socket becomes wriable
   */
  public function _onWritable(EvIo $w) {
    $this->timeout_watcher->stop();
    $w->stop();

    $in = implode("\r\n", [
      "{$this->method} {$this->resource} HTTP/1.1",
      "Host: {$this->Host}",
      'Connection: Close',
    ]) . "\r\n\r\n";

    if (!socket_write($this->socket, $in, strlen($in))) {
      trigger_error("Failed writing $in to socket", E_USER_ERROR);
      return;
    }

    $loop = $this->http_client->getLoop();
    $this->read_watcher = $loop->io($this->socket,
      Ev::READ, [$this, '_onReadable']);

    // Continue running the loop
    $loop->run();
  }

  /**
   * Callback which is called when the socket becomes readable
   */
  public function _onReadable(EvIo $w) {
    // recv() 20 bytes in non-blocking mode
    $ret = socket_recv($this->socket, $out, 20, MSG_DONTWAIT);

    if ($ret) {
      // Still have data to read. Append the read chunk to the buffer.
      $this->buffer .= $out;
    } elseif ($ret === 0) {
      // All is read
      printf("\n<<<<\n%s\n>>>>", rtrim($this->buffer));
      fflush(STDOUT);
      $w->stop();
      $this->close();
      return;
    }

    // Caught EINPROGRESS, EAGAIN, or EWOULDBLOCK
    if (in_array(socket_last_error(), static::$e_nonblocking)) {
      return;
    }

    $w->stop();
    $this->close();
  }
}

/////////////////////////////////////
class MyHttpClient {
  /// @var array Instances of MyHttpRequest
  private $requests = [];
  /// @var EvLoop
  private $loop;

  public function __construct() {
    // Each HTTP client runs its own event loop
    $this->loop = new EvLoop();
  }

  public function __destruct() {
    $this->loop->stop();
  }

  /**
   * @return EvLoop
   */
  public function getLoop() {
    return $this->loop;
  }

  /**
   * Adds a pending request
   */
  public function addRequest(MyHttpRequest $r) {
    $this->requests []= $r;
  }

  /**
   * Dispatches all pending requests
   */
  public function run() {
    $this->loop->run();
  }
}


/////////////////////////////////////
// Usage
$client = new MyHttpClient();
foreach (range(1, 10) as $i) {
  $client->addRequest(new MyHttpRequest($client, 'my-Host.local', '/test.php?a=' . $i, 'GET'));
}
$client->run();

Essai

Supposons que le script http://my-Host.local/test.php imprime le dump de $_GET:

<?php
echo 'GET: ', var_export($_GET, true), PHP_EOL;

La sortie de la commande php http-client.php sera alors semblable à celle-ci:

<<<<
HTTP/1.1 200 OK
Server: nginx/1.10.1
Date: Fri, 02 Dec 2016 12:39:54 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
X-Powered-By: PHP/7.0.13-pl0-gentoo

1d
GET: array (
  'a' => '3',
)

0
>>>>
<<<<
HTTP/1.1 200 OK
Server: nginx/1.10.1
Date: Fri, 02 Dec 2016 12:39:54 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
X-Powered-By: PHP/7.0.13-pl0-gentoo

1d
GET: array (
  'a' => '2',
)

0
>>>>
...

(coupé)

Notez que, dans PHP 5, l'extension des sockets peut enregistrer les avertissements pour EINPROGRESS, EAGAIN et EWOULDBLOCKerrno valeurs. Il est possible d’éteindre les journaux avec

error_reporting(E_ERROR);

Concernant "le reste" du code

Je veux juste faire quelque chose comme file_get_contents(), mais pas attendre que la requête se termine avant d'exécuter le reste de mon code.

Le code qui est supposé fonctionner en parallèle avec les requêtes réseau peut être exécuté dans un callback de Event timer , ou de Ev inactif , par exemple. Vous pouvez facilement le comprendre en regardant les exemples mentionnés ci-dessus. Sinon, je vais ajouter un autre exemple :)

2
Ruslan Osmanov

Voici un exemple de travail, lancez-le et ouvrez storage.txt par la suite, pour vérifier le résultat magique

<?php
    function curlGet($target){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $target);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $result = curl_exec ($ch);
        curl_close ($ch);
        return $result;
    }

    // Its the next 3 lines that do the magic
    ignore_user_abort(true);
    header("Connection: close"); header("Content-Length: 0");
    echo str_repeat("s", 100000); flush();

    $i = $_GET['i'];
    if(!is_numeric($i)) $i = 1;
    if($i > 4) exit;
    if($i == 1) file_put_contents('storage.txt', '');

    file_put_contents('storage.txt', file_get_contents('storage.txt') . time() . "\n");

    sleep(5);
    curlGet($_SERVER['HTTP_Host'] . $_SERVER['SCRIPT_NAME'] . '?i=' . ($i + 1));
    curlGet($_SERVER['HTTP_Host'] . $_SERVER['SCRIPT_NAME'] . '?i=' . ($i + 1));
1
AlexTR

Client HTTP asynchrone ReactPHP
https://github.com/shuchkin/react-http-client

Installer via Composer

$ composer require shuchkin/react-http-client

Async HTTP GET

// get.php
$loop = \React\EventLoop\Factory::create();

$http = new \Shuchkin\ReactHTTP\Client( $loop );

$http->get( 'https://tools.ietf.org/rfc/rfc2068.txt' )->then(
    function( $content ) {
        echo $content;
    },
    function ( \Exception $ex ) {
        echo 'HTTP error '.$ex->getCode().' '.$ex->getMessage();
    }
);

$loop->run();

Exécuter php en mode CLI

$ php get.php
1
Sergey Shuchkin

Voici ma propre fonction PHP lorsque je fais POST vers une URL spécifique d'une page .... Exemple: *** utilisation de ma fonction ...

    <?php
        parse_str("[email protected]&subject=this is just a test");
        $_POST['email']=$email;
        $_POST['subject']=$subject;
        echo HTTP_POST("http://example.com/mail.php",$_POST);***

    exit;
    ?>
    <?php
    /*********HTTP POST using FSOCKOPEN **************/
    // by ArbZ

function HTTP_Post($URL,$data, $referrer="") {

    // parsing the given URL
    $URL_Info=parse_url($URL);

    // Building referrer
    if($referrer=="") // if not given use this script as referrer
        $referrer=$_SERVER["SCRIPT_URI"];

    // making string from $data
    foreach($data as $key=>$value)
        $values[]="$key=".urlencode($value);
        $data_string=implode("&",$values);

    // Find out which port is needed - if not given use standard (=80)
    if(!isset($URL_Info["port"]))
        $URL_Info["port"]=80;

    // building POST-request: HTTP_HEADERs
    $request.="POST ".$URL_Info["path"]." HTTP/1.1\n";
    $request.="Host: ".$URL_Info["Host"]."\n";
    $request.="Referer: $referer\n";
    $request.="Content-type: application/x-www-form-urlencoded\n";
    $request.="Content-length: ".strlen($data_string)."\n";
    $request.="Connection: close\n";
    $request.="\n";
    $request.=$data_string."\n";

    $fp = fsockopen($URL_Info["Host"],$URL_Info["port"]);
    fputs($fp, $request);
    while(!feof($fp)) {
        $result .= fgets($fp, 128);
    }
    fclose($fp); //$eco = nl2br();


    function getTextBetweenTags($string, $tagname) {
        $pattern = "/<$tagname ?.*>(.*)<\/$tagname>/";
        preg_match($pattern, $string, $matches);
        return $matches[1];
    }
    //STORE THE FETCHED CONTENTS to a VARIABLE, because its way better and fast...
    $str = $result;
    $txt = getTextBetweenTags($str, "span"); $eco = $txt;  $result = explode("&",$result);
    return $result[1];
    <span style=background-color:LightYellow;color:blue>".trim($_GET['em'])."</span>
    </pre> "; 
}
</pre>
1
i am ArbZ

Je trouve ce paquet très utile et très simple: https://github.com/amphp/parallel-functions

<?php

use function Amp\ParallelFunctions\parallelMap;
use function Amp\Promise\wait;

$responses = wait(parallelMap([
    'https://google.com/',
    'https://github.com/',
    'https://stackoverflow.com/',
], function ($url) {
    return file_get_contents($url);
}));

Il chargera les 3 urls en parallèle. Vous pouvez également utiliser des méthodes d'instance de classe dans la fermeture.

Par exemple, j'utilise l'extension Laravel basée sur ce paquetage https://github.com/spatie/laravel-collection-macros#parallelmap

Voici mon code:

    /**
     * Get domains with all needed data
     */
    protected function getDomainsWithdata(): Collection
    {
        return $this->opensrs->getDomains()->parallelMap(function ($domain) {
            $contact = $this->opensrs->getDomainContact($domain);
            $contact['domain'] = $domain;
            return $contact;
        }, 10);
    }

Il charge toutes les données nécessaires dans 10 threads parallèles et au lieu de 50 secondes sans asynchronisation, il termine en seulement 8 secondes.

0
Vedmant