1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace cvweiss; |
4
|
|
|
|
5
|
|
|
class Guzzler |
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
|
|
|
} |
124
|
|
|
} |
125
|
|
|
|