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