Web

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

Есть ли способ в 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

Что такое локальное хранилище и как его используют в программировании

Web

Есть ли в PHP потоки

Web

Есть ли способ обновить расширение через composer и обойти ошибку ограничения памяти в Magento2

Селениум: тестирование веб-приложений и другие полезные функции
Web

Селениум: тестирование веб-приложений и другие полезные функции