web-dev-qa-db-fra.com

Comment faire une requête GET asynchrone en PHP?

Je souhaite faire une simple requête GET à un autre script sur un serveur différent. Comment puis-je faire cela?

Dans un cas, j'ai juste besoin de demander un script externe sans avoir besoin de sortie.

make_request('http://www.externalsite.com/script1.php?variable=45'); //example usage

Dans le second cas, je dois obtenir la sortie texte.

$output = make_request('http://www.externalsite.com/script2.php?variable=45');
echo $output; //string output

Pour être honnête, je ne veux pas perdre mon temps avec CURL car ce n’est pas vraiment son travail. Je ne veux pas non plus utiliser http_get car je n'ai pas les extensions PECL.

Est-ce que fsockopen fonctionnerait? Si tel est le cas, comment puis-je procéder sans lire le contenu du fichier? N'y a-t-il pas d'autre moyen?

Merci a tous

Mettre à jour

J'aurais du ajouter, dans le premier cas, je ne veux pas attendre que le script retourne quoi que ce soit. Si je comprends bien, file_get_contents () attendra que la page se charge complètement, etc.?

88
Abs

file_get_contents fera ce que vous voulez

$output = file_get_contents('http://www.example.com/');
echo $output;

Edit: Une façon de lancer une requête GET et de la retourner immédiatement.

Cité de http://petewarden.typepad.com/searchbrowser/2008/06/how-to-post-an.html

function curl_post_async($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);
}

Cela ouvre une socket, lance une requête get, ferme immédiatement la socket et le renvoie.

50
Marquis Wang

Voici comment utiliser la réponse de Marquis avec les requêtes POST et GET:

  // $type must equal 'GET' or 'POST'
  function curl_request_async($url, $params, $type='POST')
  {
      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);

      // Data goes in the path for a GET request
      if('GET' == $type) $parts['path'] .= '?'.$post_string;

      $out = "$type ".$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";
      // Data goes in the request body for a POST request
      if ('POST' == $type && isset($post_string)) $out.= $post_string;

      fwrite($fp, $out);
      fclose($fp);
  }
32
catgofire

En ce qui concerne votre mise à jour, ne pas vouloir attendre que la page complète soit chargée. Je pense qu'une requête HTTP HEAD est ce que vous recherchez.

get_headers devrait le faire - je pense que seuls les en-têtes sont demandés, le contenu de la page complète ne sera donc pas envoyé.

"PHP/Curl: HEAD La requête prend beaucoup de temps sur certains sites" explique comment effectuer une requête HEAD à l'aide de PHP/Curl

Si vous souhaitez déclencher la demande sans bloquer le script, vous avez plusieurs possibilités, dont la complexité varie.

  • Exécutez la requête HTTP en tant que processus d'arrière-plan, php exécute un processus d'arrière-plan - vous exécuterez quelque chose comme "wget -O /dev/null $carefully_escaped_url" - ce sera spécifique à la plate-forme et vous devrez être vraiment attentif à échapper des paramètres à la commande
  • Exécution d'un script PHP en arrière-plan - essentiellement la même que la méthode de processus UNIX, mais exécution d'un script PHP plutôt que d'une commande Shell
  • Avoir une "file d'attente", en utilisant une base de données (ou quelque chose comme beanstalkd , ce qui est probablement excessif). Vous ajoutez une URL à la file d'attente et un processus en arrière-plan ou un travail périodique vérifie régulièrement la présence de nouveaux travaux et effectue des demandes sur l'URL.
13
dbr

Je vous recommanderais bien testé la bibliothèque PHP: curl-easy

<?php
$request = new cURL\Request('http://www.externalsite.com/script2.php?variable=45');
$request->getOptions()
    ->set(CURLOPT_TIMEOUT, 5)
    ->set(CURLOPT_RETURNTRANSFER, true);

// add callback when the request will be completed
$request->addListener('complete', function (cURL\Event $event) {
    $response = $event->response;
    $content = $response->getContent();
    echo $content;
});

while ($request->socketPerform()) {
    // do anything else when the request is processed
}
6
stil

Vous pas. Bien que PHP offre de nombreuses façons d'appeler une URL, il n'offre pas de support prêt à l'emploi pour effectuer tout type de traitement asynchrone/en mode thread par requête/cycle d'exécution. Toute méthode d’envoi d’une requête pour une URL (ou une instruction SQL, ou une requête, etc.) va attendre la réponse some kind. Pour ce faire, vous aurez besoin d’une sorte de système secondaire fonctionnant sur la machine locale (google around pour "file d’attente php")

6
Alan Storm
function make_request($url, $waitResult=true){
    $cmi = curl_multi_init();

    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

    curl_multi_add_handle($cmi, $curl);

    $running = null;
    do {
        curl_multi_exec($cmi, $running);
        sleep(.1);
        if(!$waitResult)
        break;
    } while ($running > 0);
    curl_multi_remove_handle($cmi, $curl);
    if($waitResult){
        $curlInfos = curl_getinfo($curl);
        if((int) $curlInfos['http_code'] == 200){
            curl_multi_close($cmi);
            return curl_multi_getcontent($curl);
        }
    }
    curl_multi_close($cmi);
}
4
amez

Problème intéressant. J'imagine que vous voulez simplement déclencher un processus ou une action sur l'autre serveur, mais ne vous souciez pas des résultats et souhaitez que votre script continue. Cela peut probablement être fait dans cURL, mais vous pouvez envisager d’utiliser exec() pour exécuter un autre script sur le serveur qui effectue l’appel si cURL ne peut le faire. (En général, les utilisateurs veulent les résultats de l'appel du script. Je ne sais donc pas si PHP a la capacité de déclencher le processus.) Avec exec(), vous pouvez exécuter un wget ou même un autre script PHP qui permet la demande avec file_get_conents().

3
Darryl Hein

Vous feriez mieux d’envisager d’utiliser Message Queues au lieu de méthodes conseillées .. Je suis sûr que ce sera une meilleure solution, même si cela nécessite un peu plus de travail que le simple envoi d’une requête.

2
mra214

laisse moi te montrer mon chemin :)

besoin de nodejs installé sur le serveur

(mon serveur envoie 1 000 requêtes https en 2 secondes seulement)

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

Si vous utilisez un environnement Linux, vous pouvez utiliser la commande exec de PHP pour appeler le curl linux. Voici un exemple de code permettant de créer une publication HTTP asynchrone. 

function _async_http_post($url, $json_string) {
  $run = "curl -X POST -H 'Content-Type: application/json'";
  $run.= " -d '" .$json_string. "' " . "'" . $url . "'";
  $run.= " > /dev/null 2>&1 &";
  exec($run, $output, $exit);
  return $exit == 0;
}

Ce code ne nécessite aucune bibliothèque supplémentaire PHP et peut compléter la publication http en moins de 10 millisecondes. 

2
Stranger

Pour moi la question sur les requêtes GET asynchrones est apparue parce que j’ai rencontré une situation où j’ai besoin de faire des centaines de requêtes, d’obtenir et de traiter des données result sur chaque requête et que chaque demande prend significatif millisecondes d’exécution qui conduit à l’exécution totale en minutes (!) avec le simple file_get_contents.

Dans ce cas, c'était un commentaire très utile de w_haigh sur php.net on function http://php.net/manual/en/function.curl-multi-init.php

Donc, voici ma version mise à jour et nettoyée de faire beaucoup de demandes simultanément . Pour mon cas, cela équivaut à "asynchrone". Peut-être que cela aide pour quelqu'un!

// Build the multi-curl handle, adding both $ch
$mh = curl_multi_init();

// Build the individual requests, but do not execute them
$chs = [];
$chs['ID0001'] = curl_init('http://webservice.example.com/?method=say&Word=Hello');
$chs['ID0002'] = curl_init('http://webservice.example.com/?method=say&Word=World');
// $chs[] = ...
foreach ($chs as $ch) {
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,  // Return requested content as string
        CURLOPT_HEADER => false,         // Don't save returned headers to result
        CURLOPT_CONNECTTIMEOUT => 10,    // Max seconds wait for connect
        CURLOPT_TIMEOUT => 20,           // Max seconds on all of request
        CURLOPT_USERAGENT => 'Robot YetAnotherRobo 1.0',
    ]);

    // Well, with a little more of code you can use POST queries too
    // Also, useful options above can be  CURLOPT_SSL_VERIFYHOST => 0  
    // and  CURLOPT_SSL_VERIFYPEER => false ...

    // Add every $ch to the multi-curl handle
    curl_multi_add_handle($mh, $ch);
}

// Execute all of queries simultaneously, and continue when ALL OF THEM are complete
$running = null;
do {
    curl_multi_exec($mh, $running);
} while ($running);

// Close the handles
foreach ($chs as $ch) {
    curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);

// All of our requests are done, we can now access the results
// With a help of ids we can understand what response was given
// on every concrete our request
$responses = [];
foreach ($chs as $id => $ch) {
    $responses[$id] = curl_multi_getcontent($ch);
    curl_close($ch);
}
unset($chs); // Finita, no more need any curls :-)

print_r($responses); // output results

Il est facile de réécrire ceci pour gérer POST ou d'autres types de demandes HTTP (S) ou toute combinaison de celles-ci. Et le support des cookies, redirections, http-auth, etc.

2
FlameStorm

Personne ne semble mentionner Guzzle , qui est un client HTTP PHP facilitant l'envoi de requêtes HTTP. Cela peut fonctionner avec ou sans Curl. Il peut envoyer des requêtes synchrones et asynchrones.

$client = new GuzzleHttp\Client();
$promise = $client->requestAsync('GET', 'http://httpbin.org/get');
$promise->then(
    function (ResponseInterface $res) {
        echo $res->getStatusCode() . "\n";
    },
    function (RequestException $e) {
        echo $e->getMessage() . "\n";
        echo $e->getRequest()->getMethod();
    }
);
1
zstate

Voici une adaptation de la réponse acceptée pour effectuer une requête GET simple.

Une chose à noter si le serveur effectue une réécriture d'URL, cela ne fonctionnera pas. Vous devrez utiliser un client http plus complet. 

  /**
   * Performs an async get request (doesn't wait for response)
   * Note: One limitation of this approach is it will not work if server does any URL rewriting
   */
  function async_get($url)
  {
      $parts=parse_url($url);

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

      $out = "GET ".$parts['path']." HTTP/1.1\r\n";
      $out.= "Host: ".$parts['Host']."\r\n";
      $out.= "Connection: Close\r\n\r\n";
      fwrite($fp, $out);
      fclose($fp);
  }
1
blak3r

Essayer:

//Your Code here
$pid = pcntl_fork();
if ($pid == -1) {
     die('could not fork');
}
else if ($pid)
{
echo("Bye")  
}
else
{
     //Do Post Processing
}

Cela ne fonctionnera PAS comme un module Apache, vous devez utiliser CGI.

1
LM.

J'ai trouvé ce lien intéressant pour effectuer un traitement asynchrone (obtenir une demande).

askapache

De plus, vous pouvez effectuer un traitement asynchrone en utilisant une file de messages comme par exemple beanstalkd.

1
Alfred

Suggestion: formatez une page HTML FRAMESET contenant, disons, 9 cadres. Chaque image obtiendra une "instance" différente de votre page myapp.php. Il y aura 9 threads différents fonctionnant en parallèle sur le serveur Web.

0
newbie_dude

Sur la base de ce fil, j'ai créé ceci pour mon projet codeigniter. Cela fonctionne très bien. Vous pouvez avoir n'importe quelle fonction traitée en arrière-plan.

Un contrôleur qui accepte les appels asynchrones.

class Daemon extends CI_Controller
{
    // Remember to disable CI's csrf-checks for this controller

    function index( )
    {
        ignore_user_abort( 1 );
        try
        {
            if ( strcmp( $_SERVER['REMOTE_ADDR'], $_SERVER['SERVER_ADDR'] ) != 0 && !in_array( $_SERVER['REMOTE_ADDR'], $this->config->item( 'proxy_ips' ) ) )
            {
                log_message( "error", "Daemon called from untrusted IP-address: " . $_SERVER['REMOTE_ADDR'] );
                show_404( '/daemon' );
                return;
            }

            $this->load->library( 'encrypt' );
            $params = unserialize( urldecode( $this->encrypt->decode( $_POST['data'] ) ) );
            unset( $_POST );
            $model = array_shift( $params );
            $method = array_shift( $params );
            $this->load->model( $model );
            if ( call_user_func_array( array( $this->$model, $method ), $params ) === FALSE )
            {
                log_message( "error", "Daemon could not call: " . $model . "::" . $method . "()" );
            }
        }
        catch(Exception $e)
        {
            log_message( "error", "Daemon has error: " . $e->getMessage( ) . $e->getFile( ) . $e->getLine( ) );
        }
    }
}

Et une bibliothèque qui fait les appels asynchrones

class Daemon
{
    public function execute_background( /* model, method, params */ )
    {
        $ci = &get_instance( );
        // The callback URL (its ourselves)
        $parts = parse_url( $ci->config->item( 'base_url' ) . "/daemon" );
        if ( strcmp( $parts['scheme'], 'https' ) == 0 )
        {
            $port = 443;
            $Host = "ssl://" . $parts['Host'];
        }
        else 
        {
            $port = 80;
            $Host = $parts['Host'];
        }
        if ( ( $fp = fsockopen( $Host, isset( $parts['port'] ) ? $parts['port'] : $port, $errno, $errstr, 30 ) ) === FALSE )
        {
            throw new Exception( "Internal server error: background process could not be started" );
        }
        $ci->load->library( 'encrypt' );
        $post_string = "data=" . urlencode( $ci->encrypt->encode( serialize( func_get_args( ) ) ) );
        $out = "POST " . $parts['path'] . " HTTP/1.1\r\n";
        $out .= "Host: " . $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";
        $out .= $post_string;
        fwrite( $fp, $out );
        fclose( $fp );
    }
}

Cette méthode peut être appelée pour traiter n'importe quel model :: method () en "arrière-plan". Il utilise des arguments variables.

$this->load->library('daemon');
$this->daemon->execute_background( 'model', 'method', $arg1, $arg2, ... );
0
Patrick Savalle

Juste quelques corrections sur les scripts postés ci-dessus. Ce qui suit fonctionne pour moi

function curl_request_async($url, $params, $type='GET')
    {
        $post_params = array();
        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);
        echo print_r($parts, TRUE);
        $fp = fsockopen($parts['Host'],
            (isset($parts['scheme']) && $parts['scheme'] == 'https')? 443 : 80,
            $errno, $errstr, 30);

        $out = "$type ".$parts['path'] . (isset($parts['query']) ? '?'.$parts['query'] : '') ." 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";
        // Data goes in the request body for a POST request
        if ('POST' == $type && isset($post_string)) $out.= $post_string;
        fwrite($fp, $out);
        fclose($fp);
    }
0
A23

Pour PHP 5.5 +, mpyw/co est la solution ultime. Cela fonctionne comme s'il s'agissait de tj/co en JavaScript.

Exemple

Supposons que vous souhaitiez télécharger les avatars de plusieurs utilisateurs GitHub spécifiés. Les étapes suivantes sont requises pour chaque utilisateur.

  1. Obtenir le contenu de http://github.com/mpyw (GET HTML)
  2. Trouvez <img class="avatar" src="..."> et demandez-le (OBTENIR UNE IMAGE)

---: En attente de ma réponse
...: Attente d'une autre réponse dans des flux parallèles

De nombreux scripts basés sur curl_multi nous fournissent déjà les flux suivants.

        /-----------GET HTML\  /--GET IMAGE.........\
       /                     \/                      \ 
[Start] GET HTML..............----------------GET IMAGE [Finish]
       \                     /\                      /
        \-----GET HTML....../  \-----GET IMAGE....../

Cependant, ce n'est pas assez efficace. Voulez-vous réduire les temps d'attente sans valeur ...?

        /-----------GET HTML--GET IMAGE\
       /                                \            
[Start] GET HTML----------------GET IMAGE [Finish]
       \                                /
        \-----GET HTML-----GET IMAGE.../

Oui, c'est très facile avec mpyw/co. Pour plus de détails, visitez la page du référentiel.

0
mpyw