Есть ли способ в PHP выполнять асинхронные HTTP-вызовы? Меня не волнует ответ, я просто хочу сделать что-то, подобное file_get_contents(), но не ждать завершения запроса перед выполнением остальной части моего кода. Это было бы очень полезно для инициирования «событий» в моем приложении или запуска длительных процессов.
Есть идеи?
Ответ 1
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);
}
Ответ 2
Если вы контролируете цель, которую хотите вызвать асинхронно (например, ваш собственный "longtask.php"), вы можете закрыть соединение с этого конца, и оба скрипта будут выполняться параллельно. Это работает следующим образом:
quick.php открывает longtask.php через cURL (здесь нет никакой магии), longtask.php закрывает соединение и продолжает работу (магия!). cURL возвращается к quick.php, когда соединение закрыто.
Обе задачи продолжаются параллельно.
Я попробовал это, и это работает просто отлично. Но 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();
Ответ 3
Вы можете пойти на хитрость, используя exec() для вызова чего-то, что может выполнять HTTP-запросы, например, wget, но вы должны направить весь вывод программы куда-то, например, в файл или /dev/null, иначе процесс PHP будет ждать этого вывода.
Если вы хотите полностью отделить процесс от потока apache, попробуйте сделать что-то вроде этого (я не уверен в этом, но, надеюсь, вы поняли идею):
exec('bash -c "wget -O (url идет здесь) > /dev/null 2>&1 &"');
Это не очень приятное занятие, и вам, вероятно, понадобится что-то вроде задания cron, вызывающего скрипт heartbeat, который запрашивает реальную очередь событий базы данных для выполнения реальных асинхронных событий.
Ответ 4
По состоянию на 2018 год Guzzle стала стандартной библиотекой дефакто для HTTP-запросов, используемой в нескольких современных фреймворках. Она написана на чистом PHP и не требует установки каких-либо пользовательских расширений.
Она может очень хорошо выполнять асинхронные HTTP-запросы и даже объединять их в пулы, например, когда вам нужно сделать 100 HTTP-запросов, но вы не хотите выполнять более 5 одновременно.
Пример одновременного запроса:
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
$client = new Client(['base_uri' => 'http://httpbin.org/']);
// Инициировать каждый запрос, но не блокировать
$promises = [
'image' => $client->getAsync('/image'),
'png' => $client->getAsync('/image/png'),
'jpeg' => $client->getAsync('/image/jpeg'),
'webp' => $client->getAsync('/image/webp')
];
// Дождитесь завершения всех запросов. Выбрасывает исключение ConnectException
// если какой-либо из запросов не прошел
$results = Promise\unwrap($promises);
// Дождитесь завершения запросов, даже если некоторые из них не будут выполнены
$results = Promise\settle($promises)->wait();
// Вы можете получить доступ к каждому результату, используя ключ, предоставленный функции unwrap.
echo $results['image']['value']->getHeader('Content-Length')[0];
echo $results['png']['value']->getHeader('Content-Length')[0];
Ответ 5
Все довольно просто:
<?php
$request = new cURL\Request('http://yahoo.com/');
$request->getOptions()->set(CURLOPT_RETURNTRANSFER, true);
// Укажите функцию, которая будет вызвана, когда ваш запрос будет завершен
$request->addListener('complete', function (cURL\Event $event) {
$response = $event->response;
$httpCode = $response->getInfo(CURLINFO_HTTP_CODE);
$html = $response->getContent();
echo "\nDone.\n";
});
// Цикл ниже будет выполняться до тех пор, пока запрос обрабатывается
$timeStart = microtime(true);
while ($request->socketPerform()) {
printf("Running time: %dms \r", (microtime(true) - $timeStart)*1000);
// Здесь вы можете делать все остальное, пока ваш запрос находится в процессе выполнения
}
Ответ 6
Позвольте мне показать вам мой путь :)
Требуется 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");
// { делайте свою работу, пока выполняются запросы get... }
?>
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); // максимальное время выполнения (в мс)
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);
}
Web