PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/rolling-curl.class.php

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