@@ -4,121 +4,121 @@ |
||
| 4 | 4 | |
| 5 | 5 | class Guzzler |
| 6 | 6 | { |
| 7 | - private $curl; |
|
| 8 | - private $handler; |
|
| 9 | - private $client; |
|
| 10 | - private $concurrent = 0; |
|
| 11 | - private $maxConcurrent; |
|
| 12 | - private $usleep; |
|
| 13 | - private $lastHeaders = []; |
|
| 14 | - private $etagTTL = 86400; |
|
| 15 | - |
|
| 16 | - public function __construct($maxConcurrent = 10, $usleep = 100000, $userAgent = 'cvweiss/guzzler/', $curlOptions = []) |
|
| 17 | - { |
|
| 18 | - $curlOptions = $curlOptions == [] ? [CURLOPT_FRESH_CONNECT => false] : $curlOptions; |
|
| 19 | - |
|
| 20 | - $this->curl = new \GuzzleHttp\Handler\CurlMultiHandler(); |
|
| 21 | - $this->handler = \GuzzleHttp\HandlerStack::create($this->curl); |
|
| 22 | - $this->client = new \GuzzleHttp\Client(['curl' => $curlOptions, 'connect_timeout' => 10, 'timeout' => 10, 'handler' => $this->handler, 'headers' => ['User-Agent' => $userAgent]]); |
|
| 23 | - $this->maxConcurrent = max($maxConcurrent, 1); |
|
| 24 | - $this->usleep = max(0, min(1000000, (int) $usleep)); |
|
| 25 | - } |
|
| 26 | - |
|
| 27 | - public function tick() |
|
| 28 | - { |
|
| 29 | - $ms = microtime(); |
|
| 30 | - do { |
|
| 31 | - $this->curl->tick(); |
|
| 32 | - if ($this->concurrent >= $this->maxConcurrent) usleep(max(1, min(1000000, $this->usleep))); |
|
| 33 | - } while ($this->concurrent >= $this->maxConcurrent); |
|
| 34 | - return max(0, microtime() - $ms); |
|
| 35 | - } |
|
| 36 | - |
|
| 37 | - public function finish() |
|
| 38 | - { |
|
| 39 | - $ms = microtime(); |
|
| 40 | - $this->curl->execute(); |
|
| 41 | - return max(0, microtime() - $ms); |
|
| 42 | - } |
|
| 43 | - |
|
| 44 | - public function inc() |
|
| 45 | - { |
|
| 46 | - $this->concurrent++; |
|
| 47 | - } |
|
| 48 | - |
|
| 49 | - public function dec() |
|
| 50 | - { |
|
| 51 | - $this->concurrent--; |
|
| 52 | - } |
|
| 53 | - |
|
| 54 | - public function call($uri, $fulfilled, $rejected, $params = [], $setup = [], $callType = 'GET', $body = null) |
|
| 55 | - { |
|
| 56 | - $this->verifyCallable($fulfilled); |
|
| 57 | - $this->verifyCallable($rejected); |
|
| 58 | - |
|
| 59 | - while ($this->concurrent >= $this->maxConcurrent) $this->tick(); |
|
| 60 | - |
|
| 61 | - $params['uri'] = $uri; |
|
| 62 | - $params['fulfilled'] = $fulfilled; |
|
| 63 | - $params['rejected'] = $rejected; |
|
| 64 | - $params['setup'] = $setup; |
|
| 65 | - $params['callType'] = $callType; |
|
| 66 | - $params['body'] = $body; |
|
| 67 | - |
|
| 68 | - $redis = $this->applyEtag($setup, $params); |
|
| 69 | - |
|
| 70 | - $guzzler = $this; |
|
| 71 | - $request = new \GuzzleHttp\Psr7\Request($callType, $uri, $setup, $body); |
|
| 72 | - $this->client->sendAsync($request)->then( |
|
| 73 | - function($response) use (&$guzzler, $fulfilled, &$params, $redis) { |
|
| 74 | - $guzzler->dec(); |
|
| 75 | - $content = (string) $response->getBody(); |
|
| 76 | - $this->lastHeaders = array_change_key_case($response->getHeaders()); |
|
| 77 | - $this->applyEtagPost($this->lastHeaders, $params['uri'], $redis); |
|
| 78 | - $fulfilled($guzzler, $params, $content); |
|
| 79 | - }, |
|
| 80 | - function($connectionException) use (&$guzzler, &$rejected, &$params) { |
|
| 81 | - $guzzler->dec(); |
|
| 82 | - $response = $connectionException->getResponse(); |
|
| 83 | - $this->lastHeaders = $response == null ? [] : array_change_key_case($response->getHeaders()); |
|
| 84 | - $params['content'] = method_exists($response, "getBody") ? (string) $response->getBody() : ""; |
|
| 85 | - $rejected($guzzler, $params, $connectionException); |
|
| 86 | - }); |
|
| 87 | - $this->inc(); |
|
| 88 | - } |
|
| 89 | - |
|
| 90 | - protected function applyEtag(&$setup, $params) |
|
| 91 | - { |
|
| 92 | - $redis = isset($setup['etag']) ? $setup['etag'] : null; |
|
| 93 | - if ($redis !== null && $params['callType'] == 'GET') { |
|
| 94 | - $etag = $redis->get("guzzler:etags:" . $params['uri']); |
|
| 95 | - if ($etag != "") $setup['If-None-Match'] = $etag; |
|
| 96 | - } |
|
| 97 | - unset($setup['etag']); |
|
| 98 | - return $redis; |
|
| 99 | - } |
|
| 100 | - |
|
| 101 | - protected function applyEtagPost($headers, $uri, $redis) |
|
| 102 | - { |
|
| 103 | - if (isset($headers['etag']) && $redis !== null) { |
|
| 104 | - $redis->setex("guzzler:etags:$uri", $this->etagTTL, $headers['etag'][0]); |
|
| 105 | - } |
|
| 106 | - } |
|
| 107 | - |
|
| 108 | - public function verifyCallable($callable) |
|
| 109 | - { |
|
| 110 | - if (!is_callable($callable)) { |
|
| 111 | - throw new \InvalidArgumentException(print_r($callable, true) . " is not a callable function"); |
|
| 112 | - } |
|
| 113 | - } |
|
| 114 | - |
|
| 115 | - public function getLastHeaders() |
|
| 116 | - { |
|
| 117 | - return $this->lastHeaders; |
|
| 118 | - } |
|
| 119 | - |
|
| 120 | - public function setEtagTTL($ttl) |
|
| 121 | - { |
|
| 122 | - $this->etagTTL = $ttl; |
|
| 123 | - } |
|
| 7 | + private $curl; |
|
| 8 | + private $handler; |
|
| 9 | + private $client; |
|
| 10 | + private $concurrent = 0; |
|
| 11 | + private $maxConcurrent; |
|
| 12 | + private $usleep; |
|
| 13 | + private $lastHeaders = []; |
|
| 14 | + private $etagTTL = 86400; |
|
| 15 | + |
|
| 16 | + public function __construct($maxConcurrent = 10, $usleep = 100000, $userAgent = 'cvweiss/guzzler/', $curlOptions = []) |
|
| 17 | + { |
|
| 18 | + $curlOptions = $curlOptions == [] ? [CURLOPT_FRESH_CONNECT => false] : $curlOptions; |
|
| 19 | + |
|
| 20 | + $this->curl = new \GuzzleHttp\Handler\CurlMultiHandler(); |
|
| 21 | + $this->handler = \GuzzleHttp\HandlerStack::create($this->curl); |
|
| 22 | + $this->client = new \GuzzleHttp\Client(['curl' => $curlOptions, 'connect_timeout' => 10, 'timeout' => 10, 'handler' => $this->handler, 'headers' => ['User-Agent' => $userAgent]]); |
|
| 23 | + $this->maxConcurrent = max($maxConcurrent, 1); |
|
| 24 | + $this->usleep = max(0, min(1000000, (int) $usleep)); |
|
| 25 | + } |
|
| 26 | + |
|
| 27 | + public function tick() |
|
| 28 | + { |
|
| 29 | + $ms = microtime(); |
|
| 30 | + do { |
|
| 31 | + $this->curl->tick(); |
|
| 32 | + if ($this->concurrent >= $this->maxConcurrent) usleep(max(1, min(1000000, $this->usleep))); |
|
| 33 | + } while ($this->concurrent >= $this->maxConcurrent); |
|
| 34 | + return max(0, microtime() - $ms); |
|
| 35 | + } |
|
| 36 | + |
|
| 37 | + public function finish() |
|
| 38 | + { |
|
| 39 | + $ms = microtime(); |
|
| 40 | + $this->curl->execute(); |
|
| 41 | + return max(0, microtime() - $ms); |
|
| 42 | + } |
|
| 43 | + |
|
| 44 | + public function inc() |
|
| 45 | + { |
|
| 46 | + $this->concurrent++; |
|
| 47 | + } |
|
| 48 | + |
|
| 49 | + public function dec() |
|
| 50 | + { |
|
| 51 | + $this->concurrent--; |
|
| 52 | + } |
|
| 53 | + |
|
| 54 | + public function call($uri, $fulfilled, $rejected, $params = [], $setup = [], $callType = 'GET', $body = null) |
|
| 55 | + { |
|
| 56 | + $this->verifyCallable($fulfilled); |
|
| 57 | + $this->verifyCallable($rejected); |
|
| 58 | + |
|
| 59 | + while ($this->concurrent >= $this->maxConcurrent) $this->tick(); |
|
| 60 | + |
|
| 61 | + $params['uri'] = $uri; |
|
| 62 | + $params['fulfilled'] = $fulfilled; |
|
| 63 | + $params['rejected'] = $rejected; |
|
| 64 | + $params['setup'] = $setup; |
|
| 65 | + $params['callType'] = $callType; |
|
| 66 | + $params['body'] = $body; |
|
| 67 | + |
|
| 68 | + $redis = $this->applyEtag($setup, $params); |
|
| 69 | + |
|
| 70 | + $guzzler = $this; |
|
| 71 | + $request = new \GuzzleHttp\Psr7\Request($callType, $uri, $setup, $body); |
|
| 72 | + $this->client->sendAsync($request)->then( |
|
| 73 | + function($response) use (&$guzzler, $fulfilled, &$params, $redis) { |
|
| 74 | + $guzzler->dec(); |
|
| 75 | + $content = (string) $response->getBody(); |
|
| 76 | + $this->lastHeaders = array_change_key_case($response->getHeaders()); |
|
| 77 | + $this->applyEtagPost($this->lastHeaders, $params['uri'], $redis); |
|
| 78 | + $fulfilled($guzzler, $params, $content); |
|
| 79 | + }, |
|
| 80 | + function($connectionException) use (&$guzzler, &$rejected, &$params) { |
|
| 81 | + $guzzler->dec(); |
|
| 82 | + $response = $connectionException->getResponse(); |
|
| 83 | + $this->lastHeaders = $response == null ? [] : array_change_key_case($response->getHeaders()); |
|
| 84 | + $params['content'] = method_exists($response, "getBody") ? (string) $response->getBody() : ""; |
|
| 85 | + $rejected($guzzler, $params, $connectionException); |
|
| 86 | + }); |
|
| 87 | + $this->inc(); |
|
| 88 | + } |
|
| 89 | + |
|
| 90 | + protected function applyEtag(&$setup, $params) |
|
| 91 | + { |
|
| 92 | + $redis = isset($setup['etag']) ? $setup['etag'] : null; |
|
| 93 | + if ($redis !== null && $params['callType'] == 'GET') { |
|
| 94 | + $etag = $redis->get("guzzler:etags:" . $params['uri']); |
|
| 95 | + if ($etag != "") $setup['If-None-Match'] = $etag; |
|
| 96 | + } |
|
| 97 | + unset($setup['etag']); |
|
| 98 | + return $redis; |
|
| 99 | + } |
|
| 100 | + |
|
| 101 | + protected function applyEtagPost($headers, $uri, $redis) |
|
| 102 | + { |
|
| 103 | + if (isset($headers['etag']) && $redis !== null) { |
|
| 104 | + $redis->setex("guzzler:etags:$uri", $this->etagTTL, $headers['etag'][0]); |
|
| 105 | + } |
|
| 106 | + } |
|
| 107 | + |
|
| 108 | + public function verifyCallable($callable) |
|
| 109 | + { |
|
| 110 | + if (!is_callable($callable)) { |
|
| 111 | + throw new \InvalidArgumentException(print_r($callable, true) . " is not a callable function"); |
|
| 112 | + } |
|
| 113 | + } |
|
| 114 | + |
|
| 115 | + public function getLastHeaders() |
|
| 116 | + { |
|
| 117 | + return $this->lastHeaders; |
|
| 118 | + } |
|
| 119 | + |
|
| 120 | + public function setEtagTTL($ttl) |
|
| 121 | + { |
|
| 122 | + $this->etagTTL = $ttl; |
|
| 123 | + } |
|
| 124 | 124 | } |