PHP'de eşzamansız HTTP istekleri nasıl yapılır


209

PHP'de eşzamansız HTTP çağrıları yapmanın bir yolu var mı? Yanıt umrumda değil, sadece böyle bir şey yapmak istiyorumfile_get_contents() , ancak kodun geri kalanını çalıştırmadan önce isteğin bitmesini beklemiyorum. Bu benim uygulamamda bir tür "olaylar" ayarlamak veya uzun süreçleri tetiklemek için süper yararlı olacaktır.

Herhangi bir fikir?


9
bir işlev - 'curl_multi', php belgelerine bakın. Sorunlarınızı çözmelisiniz
James Butler

22
Bu yayının başlığı yanıltıcı. Ben Node.js veya AJAX isteği istekleri benzer gerçekten asenkron çağrılar aramaya geldi . Kabul edilen cevap zaman uyumsuz değildir (engeller ve geri arama sağlamaz), sadece daha hızlı bir eşzamanlı istektir. Soruyu veya kabul edilen cevabı değiştirmeyi düşünün.
Johntron

Başlıklar ve arabellek üzerinden bağlantı kullanımı ile oynamak kurşun geçirmez değildir. İşletim sistemi, tarayıcı veya PHP sürümlerinden bağımsız yeni bir yanıt gönderdim
RafaSashi

1
Eşzamansız, yanıtı önemsemediğiniz anlamına gelmez. Sadece çağrı ana iş parçacığı yürütmeyi engellemiyor demektir. Eşzamansız hala bir yanıt gerektirir, ancak yanıt başka bir yürütme iş parçacığında veya daha sonra bir olay döngüsünde işlenebilir. Bu soru, mesaj dağıtım semantiğine, mesaj sırasını önemsediğinize veya teslimat onayına bağlı olarak senkronize veya senkronize olmayan bir ateşle ve unut isteğini soruyor.
CMCDragonkai

Bence bu ateş HTTP isteğini engellemeyen modda yapmalısınız (w / c gerçekten istediğiniz şeydir) .. Çünkü bir kaynağı aradığınızda, temel olarak sunucuya ulaşıp ulaşmadığınızı bilmek istersiniz (veya sadece cevaba ihtiyacınız var). En iyi cevap gerçekten fsockopen ve akış okuma veya yazma engelleme olmayan moda ayarlamaktır. Ara ve unut gibi.
KiX Ortillan

Yanıtlar:


42

Daha önce kabul ettiğim cevap işe yaramadı. Hala yanıt bekliyordu. Bu nasıl çalışır, PHP'de nasıl eşzamansız bir GET isteği yapabilirim?

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

67
Bu zaman uyumsuz DEĞİLDİR! Özellikle diğer taraftaki sunucu kapalıysa, bu kod parçası 30 saniye boyunca asılı kalır (fsockopen'deki 5. parametre). Ayrıca fwrite yürütmek için tatlı zaman alacak (stream_set_timeout ($ fp, $ my_timeout) ile sınırlayabilirsiniz). Yapabileceğiniz en iyi şey fsockopen'de 0.1 (100 ms) ve $ my_timeout için 100 ms Yine de, risk zaman aşımı riski vardır
Chris Cinelli

3
Size async olduğunu ve 30 saniye sürmediğini garanti ederim. Bu bir zaman aşımı maks. Ayarlarınızın bu etkiye neden olarak farklı olması mümkündür, ancak bu benim için harika çalıştı.
Brent

11
@UltimateBrent Kodda, eşzamansız olduğunu gösteren hiçbir şey yok. Bir yanıt beklemiyor, ancak bu eşzamansız değil. Uzak sunucu bağlantıyı açar ve sonra askıda kalırsa, bu kod siz bu zaman aşımına ulaşana kadar 30 saniye bekler.
chmac

17
"zaman uyumsuz" çalışıyor gibi görünüyor çünkü kapatmadan önce soketten okumazsınız, böylece sunucu zamanında bir yanıt yaymasa bile askıda kalmadı. Ancak bu kesinlikle zaman uyumsuz değildir. Yazma arabelleği doluysa (büyük olasılıkla) komut dosyanız kesinlikle orada asılı kalır. Başlığınızı "yanıt beklemeden bir web sayfası isteme" gibi bir şeyle değiştirmeyi düşünmelisiniz.
howanghk

3
Bu ne async ne de kıvrılma kullanıyor, nasıl çağırmaya cesaret curl_post_asyncedersiniz ve hatta vekilleri alırsınız ...
Daniel W.

27

Eşzamansız olarak çağırmak istediğiniz hedefi (örneğin, kendi "longtask.php") kontrol ederseniz, bağlantıyı bu uçtan kapatabilirsiniz ve her iki komut dosyası da paralel olarak çalışır. Şöyle çalışır:

  1. quick.php longtask.php dosyasını cURL aracılığıyla açar (burada sihir yok)
  2. longtask.php bağlantıyı kapatır ve devam eder (büyü!)
  3. Bağlantı kapandığında cURL quick.php'ye döner
  4. Her iki görev de paralel devam ediyor

Bunu denedim ve gayet iyi çalışıyor. Ancak, işlemler arasında bazı iletişim araçları oluşturmazsanız, quick.php, longtask.php'nin ne yaptığı hakkında hiçbir şey bilmez.

Başka bir şey yapmadan önce longtask.php dosyasında bu kodu deneyin. Bağlantıyı keser, ancak yine de çalışmaya devam eder (ve herhangi bir çıkışı bastırır):

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

Kod, PHP kılavuzunun kullanıcı katkı notlarından kopyalanır ve bir miktar geliştirilir.


3
Bu işe yarar. Ancak bir MVC çerçevesi kullanıyorsanız, bu çerçevenin çağrıları engelleme ve yeniden yazma biçimi nedeniyle uygulanması zor olabilir. Örneğin, CakePHP'deki bir Denetleyicide çalışmaz
Chris Cinelli

Bu kod hakkında bir şüphe, longtask içinde yapmanız gereken işlem bu satırlardan sonra mı gitmelidir? Teşekkürler.
morgar

Mükemmel çalışmıyor. while(true);Kodunuzdan sonra eklemeyi deneyin . Sayfa askıda kalacaktır, bu hala ön planda çalıştığı anlamına gelir.
زياد

17

HTTP istekleri yapabilen bir şeyi çağırmak için exec () kullanarak hile yapabilirsiniz. wget , ancak programdan tüm çıktıları bir dosya veya / dev / null gibi bir yere yönlendirmeniz gerekir, aksi takdirde PHP işlemi bu çıktıyı bekler .

Süreci tamamen apache iş parçacığından ayırmak istiyorsanız, böyle bir şey deneyin (bundan emin değilim, ama umarım fikir alırsınız):

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

Bu hoş bir iş değil ve muhtemelen gerçek eşzamansız olaylar yapmak için gerçek bir veritabanı olay kuyruğunu yoklayan bir kalp atışı komut dosyası çağıran bir cron işi gibi bir şey isteyeceksiniz.


3
Benzer şekilde, aşağıdakileri de yaptım: exec ("curl $ url> / dev / null &");
Matt Huggins

2
Soru: Sadece 'wget' yerine 'bash -c "wget"' i çağırmanın bir yararı var mı?
Matt Huggins

2
Testlerimde, exec("curl $url > /dev/null 2>&1 &");buradaki kullanım en hızlı çözümlerden biridir. post_without_wait()Yukarıdaki "kabul edilen" cevaptaki fonksiyondan (14.8s) çok daha hızlıdır (100 tekrar için 1.9s). VE bu bir astar ...
rinogo

Daha da hızlı hale getirmek için tam yolu kullanın (örneğin / usr / bin / curl)
Putnik

betiğin bitmesini bekler mi?
cikatomo

11

2018'den itibaren Guzzle , çeşitli modern çerçevelerde kullanılan HTTP istekleri için defacto standart kitaplığı haline geldi. Saf PHP ile yazılmıştır ve herhangi bir özel uzantı kurmayı gerektirmez.

Eşzamansız HTTP çağrılarını çok güzel yapabilir ve hatta onları havuzlayabilir böyle 100 HTTP çağrısı yapmak gerektiğinde olarak değil, bir seferde en fazla 5 çalıştırmak istemiyoruz.

Eşzamanlı istek örneği

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]

Bkz. Http://docs.guzzlephp.org/tr/stable/quickstart.html#concurrent-requests


3
Ancak, bu cevap asenkron değildir. Görünüşe göre
guzzle

2
Guzzle kıvrılma takmanızı gerektirir. Aksi takdirde paralel değildir ve size paralel olmadığı konusunda herhangi bir uyarı vermez.
Velizar Hristov

@Daslicious bağlantısı için teşekkürler - evet, tamamen async değil gibi görünüyor (bir istek göndermek istediğinizde ancak sonucu umursamadığınızda) ama bir kullanıcının bu konuda birkaç gönderi sunduğu bir geçici çözüm bağlantı süresine hala izin veren ancak sonucu beklemeyen çok düşük bir istek zaman aşımı değeri ayarlamak.
Simon East

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

Bu, eşzamansız değildir, çünkü yürütmek istediğiniz işlemden çıkıncaya veya çatalana kadar exec engelliyor.
Daniel W.

6
&Sonunda fark ettiniz mi?
philfreo

Peki bu komut dosyasını o zaman engeller mi yoksa şaşırmaz mıyım?
pleshy

1
@pleshy olmayacak. ve işareti (&) komut dosyasını arka planda çalıştırmak anlamına gelir
daisura99

8

Bu kütüphaneyi kullanabilirsiniz: https://github.com/stil/curl-easy

O zaman oldukça basit:

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

Aşağıda yukarıdaki örneğin konsol çıktısını görebilirsiniz. Ne kadar zaman talebinin çalıştığını gösteren basit canlı saati gösterecektir:


animasyon


Bu soruya kabul edilen cevap olmalıdır, çünkü gerçek async olmasa bile, kabul edilenden daha iyidir ve tüm "async"
guzzle

7
  1. Düşük CURLayarını kullanarak istek kürtajını taklit etmeCURLOPT_TIMEOUT_MS

  2. ignore_user_abort(true)bağlantı kapandıktan sonra işlemeye devam edecek şekilde ayarlanır .

Bu yöntemle, OS, Tarayıcı ve PHP sürümüne çok fazla bağlı olan başlıklar ve arabellek üzerinden bağlantı işlemeyi gerçekleştirmeye gerek yoktur

Ana süreç

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

Arka plan süreci

ignore_user_abort(true);

//do something...

NB

CURL'nin bir saniyeden daha kısa bir süre içinde zaman aşımına uğramasını istiyorsanız, "Unix benzeri sistemlerde", değerin <1000 ms hatasıyla hemen "libcurl zaman aşımına uğramasına neden olan bir hata /" özellik "olmasına rağmen, CURLOPT_TIMEOUT_MS kullanabilirsiniz. cURL Hatası (28): Zaman aşımına ulaşıldı ". Bu davranışın açıklaması:

[...]

Çözüm CURLOPT_NOSIGNAL kullanarak sinyalleri devre dışı bırakmaktır

kaynaklar


Bağlantı zaman aşımını (çözüm, dns) nasıl ele alırsınız? Timeout_ms değerini 1 olarak ayarladığımda her zaman "4 ms'den sonra zaman aşımını çözme" veya bunun gibi bir şeyle sonuçlanırım
Martin Wickman

Bilmiyorum ama 4 ms bana çok hızlı geliyor ... Herhangi bir kıvrılma ayarını değiştirerek daha hızlı çözebileceğinizi sanmıyorum. Belki de hedeflenen isteği optimize etmeyi deneyin ...
RafaSashi

Tamam, ancak timeout_ms = 1, tüm istek için zaman aşımını ayarlar. Bu nedenle, çözümünüz 1 ms'den fazla sürerse, kıvrılma zaman aşımına uğrar ve isteği durdurur. Bunun nasıl çalışabileceğini göremiyorum (çözümün> 1 ms sürdüğü varsayılarak)
Martin Wickman

4

sana yolumu göstereyim :)

sunucuda nodejs kurulu olması gerekir

(sunucum 1000 https alma isteği gönderir, sadece 2 saniye sürer)

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

1
Birçok barındırma sağlayıcısının belirli PHP işlevlerinin ( popen / exec gibi ) kullanımına izin vermediğini lütfen unutmayın . Enable_functions PHP yönergesine bakın.
Eugen Mihailescu

4

Swoole uzantısı. https://github.com/matyhtf/swoole PHP için eşzamansız ve eşzamanlı ağ çerçevesi.

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

4

PHP için engellenmeyen yuvalar ve pecl uzantılarından birini kullanabilirsiniz:

Kodunuz ve bir pecl uzantısı arasında size bir soyutlama katmanı veren kitaplığı kullanabilirsiniz: https://github.com/reactphp/event-loop

Önceki kitaplığa dayanarak zaman uyumsuz http-istemcisi de kullanabilirsiniz: https://github.com/reactphp/http-client

ReactPHP'nin diğer kütüphanelerine bakın: http://reactphp.org

Zaman uyumsuz bir modele dikkat edin. Bu videoyu youtube'da izlemenizi öneririz: http://www.youtube.com/watch?v=MWNcItWuKpI


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

2

Etkinlik Uzantısı

Etkinlik uzantısı çok uygundur. Olay odaklı G / Ç için, özellikle ağ için tasarlanmış bir Libevent kütüphanesi limanıdır .

Bir dizi HTTP isteğini zamanlamaya ve bunları eşzamansız olarak çalıştırmaya izin veren örnek bir HTTP istemcisi yazdım.

Bu, Olay uzantısına dayalı örnek bir HTTP istemci sınıfıdır .

Sınıf, bir dizi HTTP isteğinin zamanlanmasına ve ardından senkronize olmayan şekilde çalıştırılmasına izin verir.

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

Bu sunucu tarafında örnek bir betiktir.

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

kullanım

php http-client.php

Örnek Çıktı

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

(Kesilmiş).

Kodun, CLI SAPI'de uzun vadeli işlemler için tasarlandığını unutmayın .


Özel protokoller için, düşük düzeyli API, yani tampon olayları , tamponlar kullanmayı düşünün . SSL / TLS iletişimleri için Event'in SSL bağlamıyla birlikte düşük düzeyli API'yi öneririm . Örnekler:


Libevent'in HTTP API'sı basit olmasına rağmen, arabellek olayları kadar esnek değildir. Örneğin, HTTP API şu anda özel HTTP yöntemlerini desteklememektedir. Ancak, düşük seviyeli API'yi kullanarak hemen hemen her protokolü uygulamak mümkündür.

Ev Uzantısı

Ben de kullanarak başka bir HTTP istemci bir örnek yazdım Ev ile uzatma prizleri içinde olmayan engelleme modu . Ev olayı genel amaçlı bir olay döngüsü olduğu için, kod Olay'a dayalı örnekten biraz daha ayrıntılıdır. Ağa özgü işlevler sağlamaz, ancak EvIoizleyicisi özellikle soket kaynağına kapsüllenmiş bir dosya tanımlayıcıyı dinleyebilir.

Bu, Ev uzantısına dayalı örnek bir HTTP istemcisidir .

Ev uzantısı basit ama güçlü bir genel amaçlı olay döngüsü uygular. Ağa özgü izleyiciler sağlamaz, ancak G / Ç izleyicisi soketlerin eşzamansız işlenmesi için kullanılabilir .

Aşağıdaki kod, HTTP isteklerinin paralel işleme için nasıl programlanabileceğini göstermektedir.

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

Test yapmak

Diyelim ki http://my-host.local/test.phpkomut dosyası dökümü yazdırıyor $_GET:

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

Ardından php http-client.phpkomutun çıktısı aşağıdakine benzer olacaktır:

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

(Kesilmiş)

PHP 5'de, Not prizler uzatma için uyarılar günlüğe EINPROGRESS, EAGAINve EWOULDBLOCK errnodeğerler. Günlükleri aşağıdakiler ile kapatmak mümkündür:

error_reporting(E_ERROR);

Kuralların "Geri Kalanı" ile ilgili

Sadece böyle bir şey yapmak istiyorum file_get_contents(), ancak kodumun geri kalanını çalıştırmadan önce isteğin bitmesini beklemiyorum.

Ağ isteklerine paralel olarak çalışması gereken kod, örneğin bir Olay zamanlayıcısının veya Ev'in boşta kalma izleyicisinin geri çağrısında yürütülebilir . Yukarıda belirtilen örnekleri izleyerek kolayca anlayabilirsiniz. Aksi takdirde başka bir örnek ekleyeceğim :)


1

İşte çalışan bir örnek, büyülü sonucu kontrol etmek için çalıştırın ve daha sonra storage.txt dosyasını açın.

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

1

İşte herhangi bir sayfanın belirli bir URL POST yaptığımda kendi PHP işlevi .... Örnek: *** benim işlev kullanımı ...

    <?php
        parse_str("email=myemail@ehehehahaha.com&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>

1

ReactPHP zaman uyumsuz http istemcisi
https://github.com/shuchkin/react-http-client

Composer ile Yükleme

$ composer require shuchkin/react-http-client

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

CLI modunda php'yi çalıştır

$ php get.php

0

Bu paketi oldukça kullanışlı ve çok basit buluyorum: 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'nin tümünü paralel olarak yükleyecektir. Sınıf örneği yöntemlerini de kapanışta kullanabilirsiniz.

Örneğin, bu pakete dayanan Laravel uzantısını kullanıyorum https://github.com/spatie/laravel-collection-macros#parallelmap

İşte benim kod:

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

Gerekli tüm verileri 10 paralel iş parçacığına yükler ve zaman uyumsuz olmadan 50 saniye yerine yalnızca 8 saniye içinde tamamlanır.


0

Symfony HttpClient, zaman uyumsuz https://symfony.com/doc/current/components/http_client.html .

Örneğin şunları yapabilirsiniz:

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.