Come effettuare richieste HTTP asincrone in PHP


209

Esiste un modo in PHP per effettuare chiamate HTTP asincrone? Non mi interessa la risposta, voglio solo fare qualcosa del generefile_get_contents() , ma non aspettare che la richiesta finisca prima di eseguire il resto del mio codice. Ciò sarebbe molto utile per impostare "eventi" di un tipo nella mia applicazione o per innescare processi lunghi.

Qualche idea?


9
una funzione - 'curl_multi', cercala nei documenti php. Dovrebbe risolvere i tuoi problemi
James Butler

22
Il titolo di questo post è fuorviante. Sono venuto alla ricerca di chiamate veramente asincrone simili alle richieste in Node.js o in una richiesta AJAX. La risposta accettata non è asincrona (blocca e non fornisce un callback), ma solo una richiesta sincrona più veloce. Valuta di cambiare la domanda o la risposta accettata.
Johntron,

Giocare con la gestione della connessione tramite header e buffer non è a prova di proiettile. Ho appena pubblicato una nuova risposta indipendente dal sistema operativo, dal browser o dalla versione di PHP
RafaSashi,

1
Asincrono non significa che non ti interessi della risposta. Significa solo che la chiamata non blocca l'esecuzione del thread principale. Asincrono richiede ancora una risposta, ma la risposta può essere elaborata in un altro thread di esecuzione o successivamente in un loop di eventi. Questa domanda richiede una richiesta di attivazione e disattivazione che può essere sincrona o asincrona a seconda della semantica della consegna dei messaggi, indipendentemente dal fatto che ti interessi all'ordine dei messaggi o dalla conferma di consegna.
CMCDragonkai

Penso che dovresti fare questa richiesta HTTP di fuoco in modalità non bloccante (w / c è quello che vuoi davvero) .. Perché quando chiami una risorsa, sostanzialmente vuoi sapere se hai raggiunto il server o meno (o qualunque motivo, hai semplicemente bisogno della risposta). La risposta migliore è davvero fsockopen e impostare la lettura o la scrittura dello stream in modalità non bloccante. È come chiamare e dimenticare.
KiX Ortillan,

Risposte:


42

La risposta che avevo precedentemente accettato non funzionava. Aspettava ancora risposte. Questo funziona però, tratto da Come faccio a fare una richiesta GET asincrona in 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);
}

67
Questo NON è asincrono! In particolare se il server dall'altra parte è inattivo questo pezzo di codice si bloccherà per 30 secondi (il 5o parametro in fsockopen). Anche l'esecuzione di fwrite impiegherà il suo dolce tempo (che puoi limitare con stream_set_timeout ($ fp, $ my_timeout). Il meglio che puoi fare è impostare un timeout basso su fsockopen su 0,1 (100ms) e $ my_timeout su 100ms Rischi però che il timeout della richiesta
Chris Cinelli,

3
Ti assicuro che è asincrono e non richiede 30 secondi. Questo è un timeout massimo. È possibile che le tue impostazioni siano diverse causando questo effetto, ma per me ha funzionato alla grande.
Brent,

11
@UltimateBrent Non c'è nulla nel codice che suggerisca che sia asincrono. Non aspetta una risposta, ma non è asincrono. Se il server remoto apre la connessione e quindi si blocca, questo codice aspetterebbe 30 secondi fino a quando non si attiva quel timeout.
chmac,

17
il motivo per cui sembra funzionare "asincrono" perché non si legge dal socket prima di chiuderlo, quindi non si è bloccato anche se il server non ha emesso una risposta in tempo. Tuttavia questo non è assolutamente asincrono. Se il buffer di scrittura è pieno (molto probabilmente) il tuo script si bloccherà sicuramente lì. Dovresti considerare di cambiare il tuo titolo in qualcosa come "richiedere una pagina web senza attendere la risposta".
howanghk,

3
Questo non è né asincrono né usa il ricciolo, come osi chiamarlo curl_post_asynce ottenere persino voti ...
Daniel W.

27

Se controlli la destinazione che vuoi chiamare in modo asincrono (ad es. Il tuo "longtask.php"), puoi chiudere la connessione da quella fine ed entrambi gli script verranno eseguiti in parallelo. Funziona così:

  1. quick.php apre longtask.php via cURL (nessuna magia qui)
  2. longtask.php chiude la connessione e continua (magia!)
  3. cURL ritorna a quick.php quando la connessione è chiusa
  4. Entrambe le attività continuano in parallelo

Ho provato questo e funziona benissimo. Ma quick.php non saprà nulla di quanto sta facendo longtask.php, a meno che non si crei un mezzo di comunicazione tra i processi.

Prova questo codice in longtask.php, prima di fare qualsiasi altra cosa. Chiuderà la connessione, ma continuerà comunque a funzionare (e sopprimerà qualsiasi output):

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();

Il codice viene copiato dalle note fornite dall'utente del manuale PHP e in qualche modo migliorato.


3
Questo funzionerebbe. Ma se si utilizza un framework MVC, potrebbe essere difficile implementarlo a causa del modo in cui questi framework intercettano e riscrivono le chiamate. Ad esempio, non funziona in un controller in CakePHP
Chris Cinelli,

Un dubbio su questo codice, il processo che devi fare in longtask deve seguire queste righe? Grazie.
Morgar,

Non funziona perfettamente Prova ad aggiungere while(true);dopo il tuo codice. La pagina si bloccherà, ciò significa che è ancora in esecuzione in primo piano.
زياد

17

Puoi fare dei trucchi usando exec () per invocare qualcosa che può fare richieste HTTP, come wget, ma devi indirizzare tutto l'output dal programma da qualche parte, come un file o / dev / null, altrimenti il ​​processo PHP attenderà quell'output .

Se vuoi separare completamente il processo dal thread di Apache, prova qualcosa del genere (non ne sono sicuro, ma spero che tu abbia l'idea):

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

Non è un buon affare, e probabilmente vorrai qualcosa come un cron job che invoca uno script heartbeat che esegue il polling di una coda di eventi del database reale per fare veri e propri eventi asincroni.


3
Allo stesso modo, ho anche fatto quanto segue: exec ("curl $ url> / dev / null &");
Matt Huggins,

2
Domanda: c'è un vantaggio nel chiamare 'bash -c "wget"' piuttosto che semplicemente "wget"?
Matt Huggins,

2
Nei miei test, l'utilizzo exec("curl $url > /dev/null 2>&1 &");è una delle soluzioni più veloci qui. È immensamente più veloce (1,9 secondi per 100 iterazioni) rispetto alla post_without_wait()funzione (14,8 secondi) nella risposta "accettata" sopra. ED è un one-liner ...
Rinogo,

Usa il percorso completo (ad esempio / usr / bin / curl) per renderlo ancora più veloce
Putnik,

questo aspetta fino a quando lo script è finito?
cikatomo,

11

A partire dal 2018, Guzzle è diventata la libreria standard defacto per le richieste HTTP, utilizzata in numerosi framework moderni. È scritto in puro PHP e non richiede l'installazione di estensioni personalizzate.

Può fare chiamate HTTP asincrone molto bene e anche raggrupparle come quando è necessario effettuare 100 chiamate HTTP, ma non si desidera eseguirne più di 5 alla volta.

Esempio di richiesta simultanea

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]

Vedi http://docs.guzzlephp.org/en/stable/quickstart.html#concurrent-requests


3
Tuttavia, questa risposta non è asincrona. a quanto pare il guzzle non lo fa
disastroso

2
Guzzle richiede l'installazione dell'arricciatura. Altrimenti non è parallelo e non ti avvisa che non è parallelo.
Velizar Hristov,

Grazie per il link @daslicious - sì, sembra che non sia completamente asincrono (come quando vuoi inviare una richiesta ma non ti interessa il risultato) ma alcuni post in quella discussione che un utente ha offerto una soluzione alternativa da impostare un valore di timeout della richiesta molto basso che permetta comunque il tempo di connessione, ma non attende il risultato.
Simon East,

9
/**
 * 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 &");
}

Questo non è asincrono perché exec sta bloccando fino a quando non si chiude o si fork il processo che si desidera eseguire.
Daniel W.

6
Hai notato il &alla fine?
philfreo,

Quindi questo bloccherebbe lo script o no, sono confuso?
pleshy

1
@pleshy non lo farà. e commerciale (&) significa eseguire lo script in background
daisura99

8

È possibile utilizzare questa libreria: https://github.com/stil/curl-easy

È piuttosto semplice quindi:

<?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
}

Di seguito puoi vedere l'output della console dell'esempio sopra. Verrà visualizzato un semplice orologio live che indica quanto tempo è in esecuzione la richiesta:


animazione


Questa dovrebbe essere la risposta accettata alla domanda perché, anche se non è vera asincrona, è meglio di quella accettata e tutte le risposte "asincrone" con guzzle (Qui puoi eseguire operazioni mentre la richiesta viene eseguita)
0ddlyoko,

7
  1. Falso un aborto di richiesta usando l' CURLimpostazione di un valore bassoCURLOPT_TIMEOUT_MS

  2. impostato ignore_user_abort(true)per continuare l'elaborazione dopo la chiusura della connessione.

Con questo metodo non è necessario implementare la gestione della connessione tramite intestazioni e buffer troppo dipendenti dalla versione del sistema operativo, del browser e di PHP

Processo principale

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');

Processo in background

ignore_user_abort(true);

//do something...

NB

Se vuoi che cURL scada in meno di un secondo, puoi usare CURLOPT_TIMEOUT_MS, sebbene ci sia un bug / "funzione" su "sistemi simili a Unix" che causa il timeout di libcurl immediatamente se il valore è <1000 ms con l'errore " Errore cURL (28): Timeout raggiunto ". La spiegazione per questo comportamento è:

[...]

La soluzione è disabilitare i segnali utilizzando CURLOPT_NOSIGNAL

risorse


Come gestite il timeout della connessione (resolver, dns)? Quando imposto timeout_ms su 1 finisco sempre con "risoluzione scaduta dopo 4 ms" o qualcosa del genere
Martin Wickman,

Non lo so, ma 4 ms suona già abbastanza velocemente per me ... Non penso che tu possa risolvere più velocemente modificando le impostazioni di arricciatura. Prova a ottimizzare la richiesta mirata forse ...
RafaSashi,

Ok, ma timeout_ms = 1 imposta il timeout per l'intera richiesta. Pertanto, se la risoluzione richiede più di 1 ms, l'arricciamento scadrà e interromperà la richiesta. Non vedo affatto come possa funzionare (supponendo che la risoluzione richieda> 1 ms).
Martin Wickman,

4

lascia che ti mostri la mia strada :)

necessita di nodejs installati sul server

(il mio server invia 1000 https richiedi richiede solo 2 secondi)

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);
}

1
Si noti che molti provider di hosting non consentono l'utilizzo di determinate funzioni PHP (come popen / exec ). Vedi direttiva PHP disable_functions.
Eugen Mihailescu,

4

L'estensione swoole. https://github.com/matyhtf/swoole Framework di rete asincrono e simultaneo per 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);

4

Puoi usare socket non bloccanti e una delle estensioni pecl per PHP:

Puoi utilizzare la libreria che ti offre un livello di astrazione tra il tuo codice e un'estensione pecl: https://github.com/reactphp/event-loop

Puoi anche usare un client http asincrono, basato sulla libreria precedente: https://github.com/reactphp/http-client

Vedi le altre librerie di ReactPHP: http://reactphp.org

Fai attenzione con un modello asincrono. Consiglio di vedere questo video su YouTube: http://www.youtube.com/watch?v=MWNcItWuKpI


3
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");

2

Estensione dell'evento

L' estensione dell'evento è molto appropriata. È una porta della libreria Libevent progettata per l'I / O guidato da eventi, principalmente per il networking.

Ho scritto un client HTTP di esempio che consente di pianificare un numero di richieste HTTP ed eseguirle in modo asincrono.

Questa è una classe client HTTP di esempio basata sull'estensione Event .

La classe consente di pianificare un numero di richieste HTTP, quindi di eseguirle in modo asincrono.

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

Questo è uno script di esempio sul lato server.

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

uso

php http-client.php

Uscita campione

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',
)
...

(Tagliati.)

Nota, il codice è progettato per l'elaborazione a lungo termine in SAP CLI .


Per i protocolli personalizzati, considerare l'utilizzo di API di basso livello, ad esempio eventi buffer , buffer . Per le comunicazioni SSL / TLS, consiglierei l'API di basso livello insieme al contesto ssl di Event . Esempi:


Sebbene l'API HTTP di Libevent sia semplice, non è flessibile come gli eventi buffer. Ad esempio, l'API HTTP al momento non supporta metodi HTTP personalizzati. Ma è possibile implementare praticamente qualsiasi protocollo utilizzando l'API di basso livello.

Ev Extension

Ho anche scritto un campione di un altro client HTTP usando l' estensione Ev con socket in modalità non bloccante . Il codice è leggermente più dettagliato dell'esempio basato su Event, perché Ev è un loop di eventi generico. Non fornisce funzioni specifiche della rete, ma il suo EvIowatcher è in grado di ascoltare un descrittore di file incapsulato nella risorsa socket, in particolare.

Questo è un client HTTP di esempio basato sull'estensione Ev .

L'estensione ev implementa un loop di eventi per uso generale semplice ma potente. Non fornisce watcher specifici della rete, ma il suo watcher I / O può essere utilizzato per l'elaborazione asincrona di socket .

Il codice seguente mostra come è possibile pianificare le richieste HTTP per l'elaborazione parallela.

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();

analisi

Supponiamo che lo http://my-host.local/test.phpscript stia stampando il dump di $_GET:

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

Quindi l'output del php http-client.phpcomando sarà simile al seguente:

<<<<
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
>>>>
...

(Tagliati)

Si noti, in PHP 5 il socket estensione può accedere avvertenze per EINPROGRESS, EAGAINe EWOULDBLOCK errnovalori. È possibile disattivare i registri con

error_reporting(E_ERROR);

Per quanto riguarda "il resto" del codice

Voglio solo fare qualcosa del genere file_get_contents(), ma non aspettare che la richiesta finisca prima di eseguire il resto del mio codice.

Il codice che dovrebbe essere eseguito in parallelo con le richieste di rete può essere eseguito all'interno di un callback di un timer di eventi , o il watcher inattivo di Ev , per esempio. Puoi facilmente capirlo guardando i campioni sopra menzionati. Altrimenti, aggiungerò un altro esempio :)


1

Ecco un esempio funzionante, basta eseguirlo e aprire storage.txt in seguito, per verificare il risultato magico

<?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

Ecco la mia funzione PHP quando eseguo POST su un URL specifico di qualsiasi pagina .... Esempio: *** utilizzo della mia funzione ...

    <?php
        parse_str("email=myemail@ehehehahaha.com&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

ReactPHP async http client
https://github.com/shuchkin/react-http-client

Installa tramite Composer

$ composer require shuchkin/react-http-client

HTTP asincrono OTTIENI

// 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();

Esegui php in modalità CLI

$ php get.php

0

Trovo questo pacchetto abbastanza utile e molto semplice: 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);
}));

Caricherà tutti e 3 gli URL in parallelo. Puoi anche usare metodi di istanza di classe nella chiusura.

Ad esempio, utilizzo l'estensione Laravel basata su questo pacchetto https://github.com/spatie/laravel-collection-macros#parallelmap

Ecco il mio codice:

    /**
     * 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);
    }

Carica tutti i dati necessari in 10 thread paralleli e invece di 50 secondi senza asincronizzazione termina in soli 8 secondi.


0

Symfony HttpClient è https://symfony.com/doc/current/components/http_client.html asincrono .

Ad esempio puoi

use Symfony\Component\HttpClient\HttpClient;

$client = HttpClient::create();
$response1 = $client->request('GET', 'https://website1');
$response2 = $client->request('GET', 'https://website1');
$response3 = $client->request('GET', 'https://website1');
//these 3 calls with return immediately
//but the requests will fire to the website1 webserver

$response1->getContent(); //this will block until content is fetched
$response2->getContent(); //same 
$response3->getContent(); //same

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.