Как сделать асинхронные HTTP-запросы в PHP

есть ли способ в PHP совершать асинхронные HTTP-вызовы? Меня не волнует ответ, я просто хочу сделать что-то вроде file_get_contents(), но не ждите завершения запроса перед выполнением остальной части моего кода. Это было бы очень полезно для запуска" событий " в моем приложении или запуска длинных процессов.

какие идеи?

16 ответов


ответ, который я ранее принял, не сработал. Он все еще ждал ответа. Это работает, хотя, взято из Как сделать асинхронный запрос GET в 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);
}

это должен рнр5, Я украла его из docs.php.net и редактировать конца.

Я использую его для мониторинга, когда на сайте клиентов происходит ошибка, он отправляет мне данные, не задерживая вывод

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

Если вы управляете целью, которую хотите вызвать асинхронно (например, ваша собственная "longtask.php"), вы можете закрыть соединение с этого конца, и оба скрипта будут работать параллельно. Он работает следующим образом:

  1. быстрая.php открывает longtask.php через cURL (здесь нет магии)
  2. работы longtask.php закрывает соединение и продолжает (magic!)
  3. cURL возвращается к быстрому.php, когда соединение закрыто
  4. обе задачи продолжают параллель

Я пробовал это, и это работает просто отлично. Но быстро.php ничего не будет знать о том, как longtask.php делает, если вы не создаете какие-то средства связи между процессами.

попробуйте этот код в longtask.php, прежде чем делать что-либо еще. Он закроет соединение, но все равно продолжит работу (и подавит любой вывод):

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

код копируется из PHP руководство пользователя способствовали Примечания и несколько улучшенный.


вы можете сделать обман, используя exec (), чтобы вызвать что-то, что может делать HTTP-запросы, например wget, но вы должны направить весь вывод из программы куда-нибудь, например файл или /dev/null, иначе процесс PHP будет ждать этого вывода.

Если вы хотите полностью отделить процесс от потока apache, попробуйте что-то вроде (Я не уверен в этом, но надеюсь, что вы поймете идею):

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

это не хороший бизнес, и вы, вероятно, хотите что-то как задание cron, вызывающее сценарий heartbeat, который опрашивает фактическую очередь событий базы данных для реальных асинхронных событий.


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

  1. подделать запрос аборта с помощью CURL настройки низкая CURLOPT_TIMEOUT_MS

  2. set ignore_user_abort(true) для продолжения обработки после закрытия соединения.

С помощью этого метода нет необходимости реализовывать обработку соединений через заголовки и буфер, слишком зависимые от ОС, браузера и версии PHP

мастер-процесс

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

фон процесс

ignore_user_abort(true);

//do something...

NB

если вы хотите, чтобы cURL тайм-аут менее чем за одну секунду, вы можете использовать CURLOPT_TIMEOUT_MS, хотя есть ошибка/ "функция" на " Unix-как системы", который вызывает libcurl тайм-аут немедленно, если значение

[...]

решение заключается в отключении сигналов с помощью CURLOPT_NOSIGNAL

ресурсы


позвольте мне показать вам мой путь :)

должен установлен nodejs на сервере

(мой сервер отправляет 1000 https get запрос занимает всего 2 секунды)

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

вы можете использовать эту библиотеку: https://github.com/stil/curl-easy

Это довольно просто:

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

ниже вы можете увидеть консольный вывод приведенного выше примера. Он отобразит простые живые часы, указывающие, сколько времени выполняется запрос:


animation


вы можете использовать неблокирующие сокеты и одно из расширений pecl для PHP:

вы можете использовать библиотеку, которая дает вам слой абстракции между вашим кодом и расширением pecl: https://github.com/reactphp/event-loop

вы также можете использовать async http-client, основанный на предыдущей библиотеке:https://github.com/reactphp/http-client

см. другие библиотеки ReactPHP:http://reactphp.org

будьте осторожны с асинхронной моделью. Рекомендую посмотреть это видео на youtube:http://www.youtube.com/watch?v=MWNcItWuKpI


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

расширение swoole. https://github.com/matyhtf/swoole Асинхронная и параллельная сетевая платформа для 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);

Расширения Событие

событие расширение очень подходит. Это порт Libevent библиотека, которая предназначена для ввода-вывода, управляемого событиями, в основном для сетей.

Я написал образец HTTP-клиента, который позволяет запланировать ряд HTTP-запросы и запускать их асинхронно.

это пример класса клиента HTTP на основе событие с розетки на неблокирующий режим. Код немного более подробный, чем образец, основанный на событии, потому что Ev является циклом событий общего назначения. Он не предоставляет сетевые функции, но его EvIo watcher способен прослушивать файловый дескриптор, инкапсулированный в ресурс сокета, в частности.

это образец HTTP-клиента на основе Ev, например. Вы можете легко понять это, наблюдая за образцами, упомянутыми выше. В противном случае, я добавлю еще один пример :)


по состоянию на 2018 год,жрать стала стандартной библиотекой defacto для HTTP-запросов, используемой в нескольких современных фреймворках. Он написан на чистом PHP и не требует установки каких-либо пользовательских расширений.

он может делать асинхронные HTTP-вызовы очень красиво, и даже бассейн их например, когда вам нужно сделать 100 HTTP-вызовов, но не хотите запускать более 5 за раз.

пример параллельного запроса

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]

посмотреть http://docs.guzzlephp.org/en/stable/quickstart.html#concurrent-requests


вот рабочий пример, просто запустите его и открытого хранения.txt после этого, чтобы проверить волшебный результат

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

вот моя собственная функция PHP, когда я отправляю сообщение на определенный URL любой страницы.... Пример: * * * использование моей функции...

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

ну, тайм-аут может быть установлен в миллисекундах, в разделе "CURLOPT_CONNECTTIMEOUT_MS" в http://www.php.net/manual/en/function.curl-setopt