Blame | Last modification | View Log | RSS feed
<?phpnamespace GuzzleHttp\Handler;use GuzzleHttp\Promise as P;use GuzzleHttp\Promise\Promise;use GuzzleHttp\Psr7;use Psr\Http\Message\RequestInterface;/*** Returns an asynchronous response using curl_multi_* functions.** When using the CurlMultiHandler, custom curl options can be specified as an* associative array of curl option constants mapping to values in the* **curl** key of the provided request options.** @property resource $_mh Internal use only. Lazy loaded multi-handle.*/class CurlMultiHandler{/** @var CurlFactoryInterface */private $factory;private $selectTimeout;private $active;private $handles = [];private $delays = [];/*** This handler accepts the following options:** - handle_factory: An optional factory used to create curl handles* - select_timeout: Optional timeout (in seconds) to block before timing* out while selecting curl handles. Defaults to 1 second.** @param array $options*/public function __construct(array $options = []){$this->factory = isset($options['handle_factory'])? $options['handle_factory'] : new CurlFactory(50);$this->selectTimeout = isset($options['select_timeout'])? $options['select_timeout'] : 1;}public function __get($name){if ($name === '_mh') {return $this->_mh = curl_multi_init();}throw new \BadMethodCallException();}public function __destruct(){if (isset($this->_mh)) {curl_multi_close($this->_mh);unset($this->_mh);}}public function __invoke(RequestInterface $request, array $options){$easy = $this->factory->create($request, $options);$id = (int) $easy->handle;$promise = new Promise([$this, 'execute'],function () use ($id) { return $this->cancel($id); });$this->addRequest(['easy' => $easy, 'deferred' => $promise]);return $promise;}/*** Ticks the curl event loop.*/public function tick(){// Add any delayed handles if needed.if ($this->delays) {$currentTime = microtime(true);foreach ($this->delays as $id => $delay) {if ($currentTime >= $delay) {unset($this->delays[$id]);curl_multi_add_handle($this->_mh,$this->handles[$id]['easy']->handle);}}}// Step through the task queue which may add additional requests.P\queue()->run();if ($this->active &&curl_multi_select($this->_mh, $this->selectTimeout) === -1) {// Perform a usleep if a select returns -1.// See: https://bugs.php.net/bug.php?id=61141usleep(250);}while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM);$this->processMessages();}/*** Runs until all outstanding connections have completed.*/public function execute(){$queue = P\queue();while ($this->handles || !$queue->isEmpty()) {// If there are no transfers, then sleep for the next delayif (!$this->active && $this->delays) {usleep($this->timeToNext());}$this->tick();}}private function addRequest(array $entry){$easy = $entry['easy'];$id = (int) $easy->handle;$this->handles[$id] = $entry;if (empty($easy->options['delay'])) {curl_multi_add_handle($this->_mh, $easy->handle);} else {$this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000);}}/*** Cancels a handle from sending and removes references to it.** @param int $id Handle ID to cancel and remove.** @return bool True on success, false on failure.*/private function cancel($id){// Cannot cancel if it has been processed.if (!isset($this->handles[$id])) {return false;}$handle = $this->handles[$id]['easy']->handle;unset($this->delays[$id], $this->handles[$id]);curl_multi_remove_handle($this->_mh, $handle);curl_close($handle);return true;}private function processMessages(){while ($done = curl_multi_info_read($this->_mh)) {$id = (int) $done['handle'];curl_multi_remove_handle($this->_mh, $done['handle']);if (!isset($this->handles[$id])) {// Probably was cancelled.continue;}$entry = $this->handles[$id];unset($this->handles[$id], $this->delays[$id]);$entry['easy']->errno = $done['result'];$entry['deferred']->resolve(CurlFactory::finish($this,$entry['easy'],$this->factory));}}private function timeToNext(){$currentTime = microtime(true);$nextTime = PHP_INT_MAX;foreach ($this->delays as $time) {if ($time < $nextTime) {$nextTime = $time;}}return max(0, $nextTime - $currentTime) * 1000000;}}