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