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

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

Любые идеи?


person Brent    schedule 23.09.2008    source источник
comment
одна функция - curl_multi, поищите ее в документации php. Должен решить твои проблемы   -  person James Butler    schedule 17.03.2011
comment
Название этого сообщения вводит в заблуждение. Я искал действительно асинхронные вызовы, похожие на запросы в Node.js или запрос AJAX. Принятый ответ не является асинхронным (он блокирует и не обеспечивает обратный вызов), а просто более быстрый синхронный запрос. Попробуйте изменить вопрос или принятый ответ.   -  person Johntron    schedule 29.07.2013
comment
Игра с обработкой соединения через заголовки и буфер небезопасна. Я только что опубликовал новый ответ, не зависящий от ОС, браузера или версии PHP.   -  person RafaSashi    schedule 21.02.2015
comment
Асинхронность не означает, что вам наплевать на ответ. Это просто означает, что вызов не блокирует выполнение основного потока. Асинхронный режим по-прежнему требует ответа, но ответ может быть обработан в другом потоке выполнения или позже в цикле событий. В этом вопросе запрашивается запрос «запустил и забыл», который может быть синхронным или асинхронным в зависимости от семантики доставки сообщения, независимо от того, заботитесь ли вы о порядке сообщения или подтверждении доставки.   -  person CMCDragonkai    schedule 05.03.2015
comment
Я думаю, вы должны сделать этот HTTP-запрос fire в неблокирующем режиме (w / c - это то, что вы действительно хотите). Потому что, когда вы вызываете ресурс, вы в основном хотите знать, достигли ли вы сервера или нет (или по какой-либо другой причине, вам просто нужен ответ). Лучшим ответом на самом деле является fsockopen и установка чтения или записи потока в неблокирующий режим. Это все равно что позвонить и забыть.   -  person KiX Ortillan    schedule 27.11.2015


Ответы (18)


Ответ, который я ранее принял, не сработал. Он все еще ждал ответов. Это работает, однако, взято из Как мне сделать асинхронный запрос 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);
}
person Community    schedule 27.05.2010
comment
Если вы посмотрите ссылку, которую вы разместили здесь, мой ответ также включает способ выполнения запросов GET. - person catgofire; 15.10.2010
comment
Это НЕ асинхронно! В частности, если сервер на другой стороне не работает, этот фрагмент кода будет зависать на 30 секунд (5-й параметр в fsockopen). Также fwrite займет свое приятное время для выполнения (которое вы можете ограничить с помощью stream_set_timeout ($ fp, $ my_timeout). Лучшее, что вы можете сделать, это установить низкий тайм-аут для fsockopen на 0,1 (100 мс) и $ my_timeout на 100 мс Однако вы рискуете, что тайм-аут запроса. - person Chris Cinelli; 25.10.2012
comment
Уверяю вас, что это асинхронно и не занимает 30 секунд. Это таймаут макс. Возможно, что ваши настройки вызывают такой эффект, но у меня это отлично сработало. - person Brent; 29.11.2012
comment
@UltimateBrent В коде нет ничего, что предполагало бы асинхронность. Он не ждет ответа, но это не асинхронно. Если удаленный сервер открывает соединение, а затем зависает, этот код будет ждать 30 секунд, пока вы не достигнете этого тайм-аута. - person chmac; 06.03.2013
comment
Я не знаю, что тебе сказать. Я использую его повсюду, и он работает по назначению. - person Brent; 23.03.2013
comment
причина, по которой он, кажется, работает асинхронно, потому что вы не читаете из сокета перед его закрытием, поэтому он не зависает, даже если сервер не отправил ответ вовремя. Однако это абсолютно не асинхронно. Если буфер записи заполнен (что очень маловероятно), ваш скрипт обязательно там зависнет. Вам следует подумать об изменении заголовка на что-то вроде запроса веб-страницы, не дожидаясь ответа. - person howanghk; 26.03.2013
comment
вот статья о фактическом асинхронном режиме: vince.shiftrunstop.com/dev/ - person Amir Uval; 08.04.2013
comment
Может быть, http://php.net/manual/de/function.http-build-query.php будет полезен для построения строки запроса ... - person idmean; 07.10.2013
comment
Это не асинхронно и не использует завиток, как вы осмеливаетесь называть это curl_post_async и получать даже положительные голоса ... - person Daniel W.; 31.10.2013
comment
Я не называл это так, я скопировал из связанного мной вопроса. Однако я обновил имя функции. - person Brent; 06.12.2013
comment
Я пытаюсь использовать его, но на самом деле запрос не отправляется каждый раз ... Когда я отлаживаю и просматриваю код, я всегда вижу запрос в журнале целевого сервера ... но при обычном использовании нет .. . Почему это могло быть? - person davidhq; 27.08.2014
comment
Как вы читаете на запрошенной странице php $ post_string (параметры)? Я пробую if (isset ($ _ GET [myvar])) $ token = $ _GET [myvar]; но пусто :( - person Hernaldo Gonzalez; 27.06.2015
comment
exec("curl $url > /dev/null 2>&1 &"); - одно из самых быстрых решений здесь (спасибо @Matt Huggins). Это намного быстрее (1,9 с на 100 итераций), чем функция post_without_wait() (14,8 с). И он не имеет тех же ограничений тайм-аута / перезаписи URL / и т. Д., Поскольку это полноценный cURL. И это однострочный ... - person rinogo; 28.01.2016
comment
@DavidKrmpotic, что случилось со мной, я добавил sleep(1); перед fclose(). Это должно сработать. - person Harpreet Bhatia; 13.07.2016
comment
@UltimateBrent Я не думаю, что вы понимаете значение async, как уже говорилось в нескольких ответах, это не имеет ничего общего с async. Возможно, это поможет: stackoverflow.com/questions/748175/ - person Arthur; 26.01.2017
comment
Работает хорошо, в то время как Guzzle, например, который рекомендуется в других потоках, не позволяет `` выстрелить и забыть '' - person nights; 08.11.2019
comment
Как, черт возьми, это принятый ответ? Это совсем не асинхронно. Он использует 100% блокировку ввода-вывода. В этом коде нет ничего асинхронного. - person Sherif; 28.02.2020
comment
Я не хочу POST ... - person RedGuy11; 22.01.2021

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

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

Я пробовал это, и он отлично работает. Но quick.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 и несколько улучшился.

person Christian Davén    schedule 13.02.2010
comment
Это сработает. Но если вы используете платформу MVC, это может быть сложно реализовать из-за того, как эта структура перехватывает и перезаписывает вызовы. Например, он не работает в контроллере в CakePHP. - person Chris Cinelli; 25.10.2012
comment
Сомневаетесь по поводу этого кода, процесс, который вам нужно выполнить в longtask, должен идти после этих строк? Спасибо. - person morgar; 26.09.2016
comment
Не работает идеально. Попробуйте добавить while(true); после кода. Страница зависнет, это означает, что она все еще работает на переднем плане. - person زياد; 10.05.2020
comment
Как мне открыть его через cURL? Как мне создать какие-то средства связи между процессами? - person RedGuy11; 22.01.2021

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

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

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

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

person Internet Friend    schedule 23.09.2008
comment
Точно так же я сделал следующее: exec (curl $ url ›/ dev / null &); - person Matt Huggins; 21.09.2009
comment
Вопрос: есть ли преимущество вызова «bash -c wget», а не просто «wget»? - person Matt Huggins; 10.11.2009
comment
В моем тестировании использование exec("curl $url > /dev/null 2>&1 &"); - одно из самых быстрых решений. Это намного быстрее (1,9 с на 100 итераций), чем функция post_without_wait() (14,8 с) в принятом ответе выше. И это однострочный ... - person rinogo; 28.01.2016
comment
Используйте полный путь (например, / usr / bin / curl), чтобы сделать его еще быстрее - person Putnik; 26.10.2017
comment
ждать, пока сценарий не будет завершен? - person cikatomo; 12.04.2020
comment
Разве exec() не отключено на большинстве общих серверов? - person RedGuy11; 22.01.2021

С 2018 года Guzzle стала фактически стандартной библиотекой для 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

person Simon East    schedule 09.07.2018
comment
Однако этот ответ не асинхронен. очевидно, что github.com этого не делает - person daslicious; 21.11.2018
comment
Guzzle требует, чтобы вы установили curl. В противном случае он не является параллельным и не дает вам никаких предупреждений о том, что он непараллельный. - person Velizar Hristov; 23.11.2018
comment
Спасибо за ссылку @daslicious - да, похоже, это не полностью асинхронно (например, когда вы хотите отправить запрос, но не заботитесь о результате), но несколько сообщений в этом потоке пользователь предложил обходной путь установка очень низкого значения тайм-аута запроса, которое по-прежнему разрешает время соединения, но не дожидается результата. - person Simon East; 24.11.2018
comment
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне вообще установить Guzzle, если до этого дойдет? - person RedGuy11; 22.01.2021
comment
composer require guzzle/guzzle добавляет в мой проект 537 файлов и 2,5 миллиона байтов нового кода! Для HTTP-клиента! Нет, спасибо. - person EricP; 13.06.2021

Вы можете использовать эту библиотеку: 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

person stil    schedule 19.05.2015
comment
Это должен быть принятый ответ на вопрос, потому что, даже если он не является истинным асинхронным, он лучше, чем принятый один и все асинхронные ответы с жужжанием (здесь вы можете выполнять операции, пока выполняется запрос) - person 0ddlyoko; 02.02.2019
comment
Принятый ответ © - person programmer403; 12.10.2020
comment
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет? - person RedGuy11; 22.01.2021

  1. Подделать запрос на прерывание с помощью CURL установки низкого CURLOPT_TIMEOUT_MS

  2. установите 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 немедленно, если значение <1000 мс с ошибкой » Ошибка cURL (28): Истекло время ожидания ". Объяснение такого поведения:

[...]

Решение - отключить сигналы с помощью CURLOPT_NOSIGNAL.

Ресурсы

person RafaSashi    schedule 21.02.2015
comment
Как вы обрабатываете тайм-аут соединения (разрешение, DNS)? Когда я устанавливаю timeout_ms на 1, я всегда получаю разрешение на тайм-аут через 4 мс или что-то в этом роде - person Martin Wickman; 03.04.2017
comment
Я не знаю, но для меня 4 мс уже звучат довольно быстро ... Я не думаю, что вы можете решить проблему быстрее, изменив какие-либо настройки curl. Возможно, попробуйте оптимизировать целевой запрос ... - person RafaSashi; 03.04.2017
comment
Хорошо, но timeout_ms = 1 устанавливает тайм-аут для всего запроса. Поэтому, если ваше решение занимает более 1 мс, то curl отключит тайм-аут и остановит запрос. Я не понимаю, как это вообще может работать (при условии, что разрешение занимает ›1 мс). - person Martin Wickman; 04.04.2017
comment
Я вообще этого не понимаю ... - person RedGuy11; 22.01.2021
comment
Хотя в этом нет особого смысла, но это работает безупречно и является отличным решением для асинхронного выполнения PHP. - person Jared Scarito; 17.06.2021

Расширение 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);
person Tony    schedule 03.03.2014
comment
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет? - person RedGuy11; 22.01.2021

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

требуется установка nodejs на сервере

(мой сервер отправляет 1000 запросов на получение https, занимает всего 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);
}
person user1031143    schedule 08.02.2012
comment
Обратите внимание, что многие хостинг-провайдеры не разрешают использование определенных функций PHP (например, popen / exec). См. Директиву PHP disable_functions. - person Eugen Mihailescu; 07.05.2016
comment
Разве exec() не отключен на большинстве общих серверов? Кроме того, мне нужно чистое PHP-решение. - person RedGuy11; 22.01.2021

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

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

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

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

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

person Roman Shamritskiy    schedule 18.06.2014
comment
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет? - person RedGuy11; 22.01.2021

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

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

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

Это образец класса клиента HTTP, основанный на расширении Event.

Класс позволяет планировать ряд HTTP-запросов, а затем запускать их асинхронно.

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

Это пример сценария на стороне сервера.

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

использование

php http-client.php

Пример вывода

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

(Обрезано.)

Обратите внимание: код предназначен для долгосрочной обработки в CLI SAPI.


Для пользовательских протоколов рассмотрите возможность использования низкоуровневого API, т. Е. событий буфера , буферы. Для связи SSL / TLS я бы рекомендовал низкоуровневый API в сочетании с ssl context. Примеры:


Хотя HTTP API Libevent прост, он не так гибок, как буферные события. Например, HTTP API в настоящее время не поддерживает пользовательские методы HTTP. Но можно реализовать практически любой протокол с помощью низкоуровневого API.

Расширение Ev

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

Это образец HTTP-клиента на основе расширения Ev.

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

В следующем коде показано, как можно запланировать параллельную обработку HTTP-запросов.

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

Тестирование

Предположим, сценарий http://my-host.local/test.php распечатывает дамп $_GET:

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

Тогда вывод команды php http-client.php будет похож на следующий:

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

(обрезано)

Обратите внимание, что в PHP 5 расширение сокетов может регистрировать предупреждения для значений EINPROGRESS, EAGAIN и EWOULDBLOCK errno. Можно отключить журналы с помощью

error_reporting(E_ERROR);

Относительно «остальной части» Кодекса

Я просто хочу сделать что-то вроде file_get_contents(), но не ждать завершения запроса перед выполнением остальной части моего кода.

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

person Ruslan Osmanov    schedule 02.12.2016

Вот рабочий пример, просто запустите его, а потом откройте storage.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));
person AlexTR    schedule 09.06.2014
comment
Вы уверены, что это асинхронный режим? Не похоже ... - person RedGuy11; 22.01.2021

Вот моя собственная функция PHP, когда я выполняю POST для определенного 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>
person i am ArbZ    schedule 13.05.2014
comment
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет? - person RedGuy11; 22.01.2021

ReactPHP async http-клиент
https://github.com/shuchkin/react-http-client

Установить через Composer

$ composer require shuchkin/react-http-client

Асинхронный 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();

Запускаем php в CLI-режиме

$ php get.php
person Sergey Shuchkin    schedule 28.12.2018
comment
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет? - person RedGuy11; 22.01.2021

Я считаю этот пакет очень полезным и очень простым: 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);
}));

Он загрузит все 3 URL-адреса параллельно. Вы также можете использовать методы экземпляра класса в закрытии.

Например, я использую расширение Laravel на основе этого пакета https://github.com/spatie/laravel-collection-macros#parallelmap

Вот мой код:

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

Он загружает все необходимые данные в 10 параллельных потоках и вместо 50 секунд без асинхронности завершает работу всего за 8 секунд.

person Vedmant    schedule 29.08.2019
comment
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет? - person RedGuy11; 22.01.2021
comment
@ RedGuy11 composer require amphp/parallel-functions - person Vedmant; 26.01.2021
comment
где мне запустить это? - person RedGuy11; 26.01.2021
comment
В терминале (консоли) - person Vedmant; 28.01.2021

Symfony HttpClient является асинхронным https://symfony.com/doc/current/components/http_client.html.

Например, вы можете

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
person nacholibre    schedule 18.04.2020
comment
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет? - person RedGuy11; 22.01.2021
comment
Это чистый php, но для работы вам понадобится расширение curl php. - person nacholibre; 22.01.2021
comment
хм ок. Я просто использовал curl_multi, хотя - person RedGuy11; 23.01.2021

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

person Akhil Sikri    schedule 30.04.2012
comment
Ставил только в шапку думал тайм-аут. Это совсем не асинхронный режим. - person Chris Cinelli; 25.10.2012
comment
Вы уверены, что это асинхронный режим? Не похоже ... - person RedGuy11; 22.01.2021

person    schedule
comment
Это не асинхронно, потому что exec блокируется до тех пор, пока вы не завершите или не ответите на процесс, который хотите запустить. - person Daniel W.; 04.11.2013
comment
Вы заметили & в конце? - person philfreo; 05.11.2013
comment
Так будет ли это блокировать сценарий или нет, я запутался? - person pleshy; 02.10.2015
comment
@pleshy не будет. амперсанд (&) означает запуск скрипта в фоновом режиме - person daisura99; 25.04.2017
comment
Разве exec() не отключен на большинстве общих серверов? - person RedGuy11; 22.01.2021

person    schedule
comment
У меня не работает. Да, файлы загружаются нормально, но все равно так же медленно, как и обычный file_get_contents(). - person RedGuy11; 22.01.2021