1
|
|
|
<?php namespace JMathai\PhpMultiCurl; |
2
|
|
|
/*if(!class_exists('MultiCurlManager')) |
|
|
|
|
3
|
|
|
include 'MultiCurlManager.php'; |
4
|
|
|
if(!class_exists('MultiCurlSequence')) |
5
|
|
|
include 'MultiCurlSequence.php'; |
6
|
|
|
if(!class_exists('MultiCurlException')) |
7
|
|
|
include 'MultiCurlException.php';*/ |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* MultiCurl multicurl http client |
11
|
|
|
* |
12
|
|
|
* @author Jaisen Mathai <[email protected]> |
13
|
|
|
*/ |
14
|
|
|
class MultiCurl |
15
|
|
|
{ |
16
|
|
|
const timeout = 3; |
17
|
|
|
private static $inst = null; |
18
|
|
|
/* @TODO make this private and add a method to set it to 0 */ |
19
|
|
|
public static $singleton = 0; |
20
|
|
|
|
21
|
|
|
private $mc; |
22
|
|
|
private $running; |
23
|
|
|
private $execStatus; |
24
|
|
|
private $sleepIncrement = 1.1; |
25
|
|
|
private $requests = array(); |
26
|
|
|
private $responses = array(); |
27
|
|
|
private $properties = array(); |
28
|
|
|
private static $timers = array(); |
29
|
|
|
|
30
|
|
|
public function __construct() |
31
|
|
|
{ |
32
|
|
|
if(self::$singleton === 0) |
33
|
|
|
{ |
34
|
|
|
throw new MultiCurlException('This class cannot be instantiated by the new keyword. You must instantiate it using: $obj = MultiCurl::getInstance();'); |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
$this->mc = curl_multi_init(); |
38
|
|
|
$this->properties = array( |
39
|
|
|
'code' => CURLINFO_HTTP_CODE, |
40
|
|
|
'time' => CURLINFO_TOTAL_TIME, |
41
|
|
|
'length'=> CURLINFO_CONTENT_LENGTH_DOWNLOAD, |
42
|
|
|
'type' => CURLINFO_CONTENT_TYPE, |
43
|
|
|
'url' => CURLINFO_EFFECTIVE_URL |
44
|
|
|
); |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
public function reset(){ |
48
|
|
|
$this->requests = array(); |
49
|
|
|
$this->responses = array(); |
50
|
|
|
self::$timers = array(); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
public function addUrl($url, $options = array()) |
54
|
|
|
{ |
55
|
|
|
$ch = curl_init($url); |
56
|
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
57
|
|
|
foreach($options as $option=>$value) |
58
|
|
|
{ |
59
|
|
|
curl_setopt($ch, $option, $value); |
60
|
|
|
} |
61
|
|
|
return $this->addCurl($ch); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
public function addCurl($ch) |
65
|
|
|
{ |
66
|
|
|
if(gettype($ch) !== 'resource') |
67
|
|
|
{ |
68
|
|
|
throw new MultiCurlInvalidParameterException('Parameter must be a valid curl handle'); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
$key = $this->getKey($ch); |
72
|
|
|
$this->requests[$key] = $ch; |
73
|
|
|
curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'headerCallback')); |
74
|
|
|
|
75
|
|
|
$code = curl_multi_add_handle($this->mc, $ch); |
76
|
|
|
$this->startTimer($key); |
77
|
|
|
|
78
|
|
|
// (1) |
79
|
|
|
if($code === CURLM_OK || $code === CURLM_CALL_MULTI_PERFORM) |
80
|
|
|
{ |
81
|
|
|
do |
82
|
|
|
{ |
83
|
|
|
$this->execStatus = curl_multi_exec($this->mc, $this->running); |
84
|
|
|
} while ($this->execStatus === CURLM_CALL_MULTI_PERFORM); |
85
|
|
|
|
86
|
|
|
return new MultiCurlManager($key); |
87
|
|
|
} |
88
|
|
|
else |
89
|
|
|
{ |
90
|
|
|
return $code; |
91
|
|
|
} |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
public function getResult($key = null) |
95
|
|
|
{ |
96
|
|
|
if($key != null) |
97
|
|
|
{ |
98
|
|
|
if(isset($this->responses[$key]['code'])) |
99
|
|
|
{ |
100
|
|
|
return $this->responses[$key]; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
$innerSleepInt = $outerSleepInt = 1; |
104
|
|
|
while($this->running && ($this->execStatus == CURLM_OK || $this->execStatus == CURLM_CALL_MULTI_PERFORM)) |
105
|
|
|
{ |
106
|
|
|
usleep(intval($outerSleepInt)); |
107
|
|
|
$outerSleepInt = intval(max(1, ($outerSleepInt*$this->sleepIncrement))); |
108
|
|
|
$ms=curl_multi_select($this->mc, 0); |
109
|
|
|
|
110
|
|
|
// bug in PHP 5.3.18+ where curl_multi_select can return -1 |
111
|
|
|
// https://bugs.php.net/bug.php?id=63411 |
112
|
|
|
if($ms === -1) |
113
|
|
|
usleep(100000); |
114
|
|
|
|
115
|
|
|
// see pull request https://github.com/jmathai/php-multi-curl/pull/17 |
116
|
|
|
// details here http://curl.haxx.se/libcurl/c/libcurl-errors.html |
117
|
|
|
if($ms >= CURLM_CALL_MULTI_PERFORM) |
118
|
|
|
{ |
119
|
|
|
do{ |
120
|
|
|
$this->execStatus = curl_multi_exec($this->mc, $this->running); |
121
|
|
|
usleep(intval($innerSleepInt)); |
122
|
|
|
$innerSleepInt = intval(max(1, ($innerSleepInt*$this->sleepIncrement))); |
123
|
|
|
}while($this->execStatus==CURLM_CALL_MULTI_PERFORM); |
124
|
|
|
$innerSleepInt = 1; |
125
|
|
|
} |
126
|
|
|
$this->storeResponses(); |
127
|
|
|
if(isset($this->responses[$key]['data'])) |
128
|
|
|
{ |
129
|
|
|
return $this->responses[$key]; |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
return null; |
133
|
|
|
} |
134
|
|
|
return false; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
public static function getSequence() |
138
|
|
|
{ |
139
|
|
|
return new MultiCurlSequence(self::$timers); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
public static function getTimers() |
143
|
|
|
{ |
144
|
|
|
return self::$timers; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
public function inject($key, $value) |
148
|
|
|
{ |
149
|
|
|
$this->$key = $value; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
private function getKey($ch) |
153
|
|
|
{ |
154
|
|
|
return (string)$ch; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
private function headerCallback($ch, $header) |
158
|
|
|
{ |
159
|
|
|
$_header = trim($header); |
160
|
|
|
$colonPos= strpos($_header, ':'); |
161
|
|
|
if($colonPos > 0) |
162
|
|
|
{ |
163
|
|
|
$key = substr($_header, 0, $colonPos); |
164
|
|
|
$val = preg_replace('/^\W+/','',substr($_header, $colonPos)); |
165
|
|
|
$this->responses[$this->getKey($ch)]['headers'][$key] = $val; |
166
|
|
|
} |
167
|
|
|
return strlen($header); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
private function storeResponses() |
171
|
|
|
{ |
172
|
|
|
while($done = curl_multi_info_read($this->mc)) |
173
|
|
|
{ |
174
|
|
|
$this->storeResponse($done); |
175
|
|
|
} |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
private function storeResponse($done, $isAsynchronous = true) |
179
|
|
|
{ |
180
|
|
|
$key = $this->getKey($done['handle']); |
181
|
|
|
$this->stopTimer($key, $done); |
182
|
|
|
if($isAsynchronous) |
183
|
|
|
$this->responses[$key]['data'] = curl_multi_getcontent($done['handle']); |
184
|
|
|
else |
185
|
|
|
$this->responses[$key]['data'] = curl_exec($done['handle']); |
186
|
|
|
|
187
|
|
|
$this->responses[$key]['response'] = $this->responses[$key]['data']; |
188
|
|
|
|
189
|
|
|
foreach($this->properties as $name => $const) |
190
|
|
|
{ |
191
|
|
|
$this->responses[$key][$name] = curl_getinfo($done['handle'], $const); |
192
|
|
|
} |
193
|
|
|
if($isAsynchronous) |
194
|
|
|
curl_multi_remove_handle($this->mc, $done['handle']); |
195
|
|
|
curl_close($done['handle']); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
private function startTimer($key) |
199
|
|
|
{ |
200
|
|
|
self::$timers[$key]['start'] = microtime(true); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
private function stopTimer($key, $done) |
204
|
|
|
{ |
205
|
|
|
self::$timers[$key]['end'] = microtime(true); |
206
|
|
|
self::$timers[$key]['api'] = curl_getinfo($done['handle'], CURLINFO_EFFECTIVE_URL); |
207
|
|
|
self::$timers[$key]['time'] = curl_getinfo($done['handle'], CURLINFO_TOTAL_TIME); |
208
|
|
|
self::$timers[$key]['code'] = curl_getinfo($done['handle'], CURLINFO_HTTP_CODE); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
public static function getInstance() |
212
|
|
|
{ |
213
|
|
|
if(self::$inst == null) |
214
|
|
|
{ |
215
|
|
|
self::$singleton = 1; |
216
|
|
|
self::$inst = new MultiCurl(); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
return self::$inst; |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/* |
224
|
|
|
* Credits: |
225
|
|
|
* - (1) Alistair pointed out that curl_multi_add_handle can return CURLM_CALL_MULTI_PERFORM on success. |
226
|
|
|
*/ |
227
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.