]>
Commit | Line | Data |
---|---|---|
ec397236 NL |
1 | <?php |
2 | /* | |
3 | Authored by Josh Fraser (www.joshfraser.com) | |
4 | Released under Apache License 2.0 | |
5 | ||
6 | Maintained by Alexander Makarov, http://rmcreative.ru/ | |
7 | ||
8 | Modified by Keyvan Minoukadeh for the Five Filters project: http://fivefilters.org | |
9 | */ | |
10 | ||
11 | /** | |
12 | * Class that represent a single curl request | |
13 | */ | |
14 | class RollingCurlRequest { | |
15 | public $url = false; | |
16 | public $url_original = false; // used for tracking redirects | |
17 | public $method = 'GET'; | |
18 | public $post_data = null; | |
19 | public $headers = null; | |
20 | public $options = null; | |
21 | ||
22 | /** | |
23 | * @param string $url | |
24 | * @param string $method | |
25 | * @param $post_data | |
26 | * @param $headers | |
27 | * @param $options | |
28 | * @return void | |
29 | */ | |
30 | function __construct($url, $method = "GET", $post_data = null, $headers = null, $options = null) { | |
31 | $this->url = $url; | |
32 | $this->url_original = $url; | |
33 | $this->method = $method; | |
34 | $this->post_data = $post_data; | |
35 | $this->headers = $headers; | |
36 | $this->options = $options; | |
37 | } | |
38 | ||
39 | /** | |
40 | * @param string $url | |
41 | * @return void | |
42 | */ | |
43 | public function set_original_url($url) { | |
44 | $this->url_original = $url; | |
45 | } | |
46 | /** | |
47 | * @return void | |
48 | */ | |
49 | public function __destruct() { | |
50 | unset($this->url, $this->url_original, $this->method, $this->post_data, $this->headers, $this->options); | |
51 | } | |
52 | } | |
53 | ||
54 | /** | |
55 | * RollingCurl custom exception | |
56 | */ | |
57 | class RollingCurlException extends Exception { | |
58 | } | |
59 | ||
60 | /** | |
61 | * Class that holds a rolling queue of curl requests. | |
62 | * | |
63 | * @throws RollingCurlException | |
64 | */ | |
65 | class RollingCurl implements Countable { | |
66 | /** | |
67 | * @var int | |
68 | * | |
69 | * Window size is the max number of simultaneous connections allowed. | |
70 | * | |
71 | * REMEMBER TO RESPECT THE SERVERS: | |
72 | * Sending too many requests at one time can easily be perceived | |
73 | * as a DOS attack. Increase this window_size if you are making requests | |
74 | * to multiple servers or have permission from the receving server admins. | |
75 | */ | |
76 | private $window_size = 5; | |
77 | ||
78 | /** | |
79 | * @var float | |
80 | * | |
81 | * Timeout is the timeout used for curl_multi_select. | |
82 | */ | |
83 | private $timeout = 10; | |
84 | ||
85 | /** | |
86 | * @var string|array | |
87 | * | |
88 | * Callback function to be applied to each result. | |
89 | */ | |
90 | private $callback; | |
91 | ||
92 | /** | |
93 | * @var array | |
94 | * | |
95 | * Set your base options that you want to be used with EVERY request. | |
96 | */ | |
97 | protected $options = array( | |
98 | CURLOPT_SSL_VERIFYPEER => 0, | |
99 | CURLOPT_RETURNTRANSFER => 1, | |
100 | CURLOPT_CONNECTTIMEOUT => 30, | |
101 | CURLOPT_TIMEOUT => 30 | |
102 | ); | |
103 | ||
104 | /** | |
105 | * @var array | |
106 | */ | |
107 | private $headers = array(); | |
108 | ||
109 | /** | |
110 | * @var Request[] | |
111 | * | |
112 | * The request queue | |
113 | */ | |
114 | private $requests = array(); | |
115 | ||
116 | /** | |
117 | * @var RequestMap[] | |
118 | * | |
119 | * Maps handles to request indexes | |
120 | */ | |
121 | private $requestMap = array(); | |
122 | ||
123 | /** | |
124 | * @param $callback | |
125 | * Callback function to be applied to each result. | |
126 | * | |
127 | * Can be specified as 'my_callback_function' | |
128 | * or array($object, 'my_callback_method'). | |
129 | * | |
130 | * Function should take three parameters: $response, $info, $request. | |
131 | * $response is response body, $info is additional curl info. | |
132 | * $request is the original request | |
133 | * | |
134 | * @return void | |
135 | */ | |
136 | function __construct($callback = null) { | |
137 | $this->callback = $callback; | |
138 | } | |
139 | ||
140 | /** | |
141 | * @param string $name | |
142 | * @return mixed | |
143 | */ | |
144 | public function __get($name) { | |
145 | return (isset($this->{$name})) ? $this->{$name} : null; | |
146 | } | |
147 | ||
148 | /** | |
149 | * @param string $name | |
150 | * @param mixed $value | |
151 | * @return bool | |
152 | */ | |
153 | public function __set($name, $value) { | |
154 | // append the base options & headers | |
155 | if ($name == "options" || $name == "headers") { | |
156 | $this->{$name} = $value + $this->{$name}; | |
157 | } else { | |
158 | $this->{$name} = $value; | |
159 | } | |
160 | return true; | |
161 | } | |
162 | ||
163 | /** | |
164 | * Count number of requests added (Countable interface) | |
165 | * | |
166 | * @return int | |
167 | */ | |
168 | public function count() { | |
169 | return count($this->requests); | |
170 | } | |
171 | ||
172 | /** | |
173 | * Add a request to the request queue | |
174 | * | |
175 | * @param Request $request | |
176 | * @return bool | |
177 | */ | |
178 | public function add($request) { | |
179 | $this->requests[] = $request; | |
180 | return true; | |
181 | } | |
182 | ||
183 | /** | |
184 | * Create new Request and add it to the request queue | |
185 | * | |
186 | * @param string $url | |
187 | * @param string $method | |
188 | * @param $post_data | |
189 | * @param $headers | |
190 | * @param $options | |
191 | * @return bool | |
192 | */ | |
193 | public function request($url, $method = "GET", $post_data = null, $headers = null, $options = null) { | |
194 | $this->requests[] = new RollingCurlRequest($url, $method, $post_data, $headers, $options); | |
195 | return true; | |
196 | } | |
197 | ||
198 | /** | |
199 | * Perform GET request | |
200 | * | |
201 | * @param string $url | |
202 | * @param $headers | |
203 | * @param $options | |
204 | * @return bool | |
205 | */ | |
206 | public function get($url, $headers = null, $options = null) { | |
207 | return $this->request($url, "GET", null, $headers, $options); | |
208 | } | |
209 | ||
210 | /** | |
211 | * Perform POST request | |
212 | * | |
213 | * @param string $url | |
214 | * @param $post_data | |
215 | * @param $headers | |
216 | * @param $options | |
217 | * @return bool | |
218 | */ | |
219 | public function post($url, $post_data = null, $headers = null, $options = null) { | |
220 | return $this->request($url, "POST", $post_data, $headers, $options); | |
221 | } | |
222 | ||
223 | /** | |
224 | * Execute processing | |
225 | * | |
226 | * @param int $window_size Max number of simultaneous connections | |
227 | * @return string|bool | |
228 | */ | |
229 | public function execute($window_size = null) { | |
230 | // rolling curl window must always be greater than 1 | |
231 | if (sizeof($this->requests) == 1) { | |
232 | return $this->single_curl(); | |
233 | } else { | |
234 | // start the rolling curl. window_size is the max number of simultaneous connections | |
235 | return $this->rolling_curl($window_size); | |
236 | } | |
237 | } | |
238 | ||
239 | /** | |
240 | * Performs a single curl request | |
241 | * | |
242 | * @access private | |
243 | * @return string | |
244 | */ | |
245 | private function single_curl() { | |
246 | $ch = curl_init(); | |
247 | $request = array_shift($this->requests); | |
248 | $options = $this->get_options($request); | |
249 | curl_setopt_array($ch, $options); | |
250 | $output = curl_exec($ch); | |
251 | $info = curl_getinfo($ch); | |
252 | ||
253 | // it's not neccesary to set a callback for one-off requests | |
254 | if ($this->callback) { | |
255 | $callback = $this->callback; | |
256 | if (is_callable($this->callback)) { | |
257 | call_user_func($callback, $output, $info, $request); | |
258 | } | |
259 | } | |
260 | else | |
261 | return $output; | |
262 | return true; | |
263 | } | |
264 | ||
265 | /** | |
266 | * Performs multiple curl requests | |
267 | * | |
268 | * @access private | |
269 | * @throws RollingCurlException | |
270 | * @param int $window_size Max number of simultaneous connections | |
271 | * @return bool | |
272 | */ | |
273 | private function rolling_curl($window_size = null) { | |
274 | if ($window_size) | |
275 | $this->window_size = $window_size; | |
276 | ||
277 | // make sure the rolling window isn't greater than the # of urls | |
278 | if (sizeof($this->requests) < $this->window_size) | |
279 | $this->window_size = sizeof($this->requests); | |
280 | ||
281 | if ($this->window_size < 2) { | |
282 | throw new RollingCurlException("Window size must be greater than 1"); | |
283 | } | |
284 | ||
285 | $master = curl_multi_init(); | |
286 | ||
287 | // start the first batch of requests | |
288 | for ($i = 0; $i < $this->window_size; $i++) { | |
289 | $ch = curl_init(); | |
290 | ||
291 | $options = $this->get_options($this->requests[$i]); | |
292 | ||
293 | curl_setopt_array($ch, $options); | |
294 | curl_multi_add_handle($master, $ch); | |
295 | ||
296 | // Add to our request Maps | |
297 | $key = (string) $ch; | |
298 | $this->requestMap[$key] = $i; | |
299 | } | |
300 | ||
301 | do { | |
302 | while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ; | |
303 | if ($execrun != CURLM_OK) | |
304 | break; | |
305 | // a request was just completed -- find out which one | |
306 | while ($done = curl_multi_info_read($master)) { | |
307 | ||
308 | // get the info and content returned on the request | |
309 | $info = curl_getinfo($done['handle']); | |
310 | $output = curl_multi_getcontent($done['handle']); | |
311 | ||
312 | // send the return values to the callback function. | |
313 | $callback = $this->callback; | |
314 | if (is_callable($callback)) { | |
315 | $key = (string) $done['handle']; | |
316 | $request = $this->requests[$this->requestMap[$key]]; | |
317 | unset($this->requestMap[$key]); | |
318 | call_user_func($callback, $output, $info, $request); | |
319 | } | |
320 | ||
321 | // start a new request (it's important to do this before removing the old one) | |
322 | if ($i < sizeof($this->requests) && isset($this->requests[$i]) && $i < count($this->requests)) { | |
323 | $ch = curl_init(); | |
324 | $options = $this->get_options($this->requests[$i]); | |
325 | curl_setopt_array($ch, $options); | |
326 | curl_multi_add_handle($master, $ch); | |
327 | ||
328 | // Add to our request Maps | |
329 | $key = (string) $ch; | |
330 | $this->requestMap[$key] = $i; | |
331 | $i++; | |
332 | } | |
333 | ||
334 | // remove the curl handle that just completed | |
335 | curl_multi_remove_handle($master, $done['handle']); | |
336 | ||
337 | } | |
338 | ||
339 | // Block for data in / output; error handling is done by curl_multi_exec | |
340 | //if ($running) curl_multi_select($master, $this->timeout); | |
341 | // removing timeout as it causes problems on Windows with PHP 5.3.5 and Curl 7.20.0 | |
342 | if ($running) curl_multi_select($master); | |
343 | ||
344 | } while ($running); | |
345 | curl_multi_close($master); | |
346 | return true; | |
347 | } | |
348 | ||
349 | ||
350 | /** | |
351 | * Helper function to set up a new request by setting the appropriate options | |
352 | * | |
353 | * @access private | |
354 | * @param Request $request | |
355 | * @return array | |
356 | */ | |
357 | private function get_options($request) { | |
358 | // options for this entire curl object | |
359 | $options = $this->__get('options'); | |
360 | // We're managing reirects in PHP - allows us to intervene and rewrite/block URLs | |
361 | // before the next request goes out. | |
362 | $options[CURLOPT_FOLLOWLOCATION] = 0; | |
363 | $options[CURLOPT_MAXREDIRS] = 0; | |
364 | //if (ini_get('safe_mode') == 'Off' || !ini_get('safe_mode')) { | |
365 | // $options[CURLOPT_FOLLOWLOCATION] = 1; | |
366 | // $options[CURLOPT_MAXREDIRS] = 5; | |
367 | //} | |
368 | $headers = $this->__get('headers'); | |
369 | // append custom headers for this specific request | |
370 | if ($request->headers) { | |
371 | $headers = $headers + $request->headers; | |
372 | } | |
373 | ||
374 | // append custom options for this specific request | |
375 | if ($request->options) { | |
376 | $options = $request->options + $options; | |
377 | } | |
378 | ||
379 | // set the request URL | |
380 | $options[CURLOPT_URL] = $request->url; | |
381 | ||
382 | if ($headers) { | |
383 | $options[CURLOPT_HTTPHEADER] = $headers; | |
384 | } | |
385 | // return response headers | |
386 | $options[CURLOPT_HEADER] = 1; | |
387 | ||
388 | // send HEAD request? | |
389 | if ($request->method == 'HEAD') { | |
390 | $options[CURLOPT_NOBODY] = 1; | |
391 | } | |
392 | ||
393 | return $options; | |
394 | } | |
395 | ||
396 | /** | |
397 | * @return void | |
398 | */ | |
399 | public function __destruct() { | |
400 | unset($this->window_size, $this->callback, $this->options, $this->headers, $this->requests); | |
401 | } | |
402 | } |