/include/twitter-async/EpiCurl.php

https://github.com/diagonalfish/allmyfriendssay · PHP · 212 lines · 182 code · 25 blank · 5 comment · 19 complexity · f6dca7bbb802f77bcd3001b530a2e26d MD5 · raw file

  1. <?php
  2. class EpiCurl
  3. {
  4. const timeout = 3;
  5. static $inst = null;
  6. static $singleton = 0;
  7. private $mc;
  8. private $msgs;
  9. private $running;
  10. private $execStatus;
  11. private $selectStatus;
  12. private $sleepIncrement = 1.1;
  13. private $requests = array();
  14. private $responses = array();
  15. private $properties = array();
  16. private static $timers = array();
  17. function __construct()
  18. {
  19. if(self::$singleton == 0)
  20. {
  21. throw new Exception('This class cannot be instantiated by the new keyword. You must instantiate it using: $obj = EpiCurl::getInstance();');
  22. }
  23. $this->mc = curl_multi_init();
  24. $this->properties = array(
  25. 'code' => CURLINFO_HTTP_CODE,
  26. 'time' => CURLINFO_TOTAL_TIME,
  27. 'length'=> CURLINFO_CONTENT_LENGTH_DOWNLOAD,
  28. 'type' => CURLINFO_CONTENT_TYPE,
  29. 'url' => CURLINFO_EFFECTIVE_URL
  30. );
  31. }
  32. public function addEasyCurl($ch)
  33. {
  34. $key = $this->getKey($ch);
  35. $this->requests[$key] = $ch;
  36. curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'headerCallback'));
  37. $done = array('handle' => $ch);
  38. $this->storeResponse($done, false);
  39. $this->startTimer($key);
  40. return new EpiCurlManager($key);
  41. }
  42. public function addCurl($ch)
  43. {
  44. $key = $this->getKey($ch);
  45. $this->requests[$key] = $ch;
  46. curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'headerCallback'));
  47. $code = curl_multi_add_handle($this->mc, $ch);
  48. $this->startTimer($key);
  49. // (1)
  50. if($code === CURLM_OK || $code === CURLM_CALL_MULTI_PERFORM)
  51. {
  52. do {
  53. $code = $this->execStatus = curl_multi_exec($this->mc, $this->running);
  54. } while ($this->execStatus === CURLM_CALL_MULTI_PERFORM);
  55. return new EpiCurlManager($key);
  56. }
  57. else
  58. {
  59. return $code;
  60. }
  61. }
  62. public function getResult($key = null)
  63. {
  64. if($key != null)
  65. {
  66. if(isset($this->responses[$key]))
  67. {
  68. return $this->responses[$key];
  69. }
  70. $innerSleepInt = $outerSleepInt = 1;
  71. while($this->running && ($this->execStatus == CURLM_OK || $this->execStatus == CURLM_CALL_MULTI_PERFORM))
  72. {
  73. usleep(intval($outerSleepInt));
  74. $outerSleepInt = intval(max(1, ($outerSleepInt*$this->sleepIncrement)));
  75. $ms=curl_multi_select($this->mc, 0);
  76. if($ms > 0)
  77. {
  78. do{
  79. $this->execStatus = curl_multi_exec($this->mc, $this->running);
  80. usleep(intval($innerSleepInt));
  81. $innerSleepInt = intval(max(1, ($innerSleepInt*$this->sleepIncrement)));
  82. }while($this->execStatus==CURLM_CALL_MULTI_PERFORM);
  83. $innerSleepInt = 1;
  84. }
  85. $this->storeResponses();
  86. if(isset($this->responses[$key]['data']))
  87. {
  88. return $this->responses[$key];
  89. }
  90. $runningCurrent = $this->running;
  91. }
  92. return null;
  93. }
  94. return false;
  95. }
  96. public static function getSequence()
  97. {
  98. return new EpiSequence(self::$timers);
  99. }
  100. public static function getTimers()
  101. {
  102. return self::$timers;
  103. }
  104. private function getKey($ch)
  105. {
  106. return (string)$ch;
  107. }
  108. private function headerCallback($ch, $header)
  109. {
  110. $_header = trim($header);
  111. $colonPos= strpos($_header, ':');
  112. if($colonPos > 0)
  113. {
  114. $key = substr($_header, 0, $colonPos);
  115. $val = preg_replace('/^\W+/','',substr($_header, $colonPos));
  116. $this->responses[$this->getKey($ch)]['headers'][$key] = $val;
  117. }
  118. return strlen($header);
  119. }
  120. private function storeResponses()
  121. {
  122. while($done = curl_multi_info_read($this->mc))
  123. {
  124. $this->storeResponse($done);
  125. }
  126. }
  127. private function storeResponse($done, $isAsynchronous = true)
  128. {
  129. $key = $this->getKey($done['handle']);
  130. $this->stopTimer($key, $done);
  131. if($isAsynchronous)
  132. $this->responses[$key]['data'] = curl_multi_getcontent($done['handle']);
  133. else
  134. $this->responses[$key]['data'] = curl_exec($done['handle']);
  135. foreach($this->properties as $name => $const)
  136. {
  137. $this->responses[$key][$name] = curl_getinfo($done['handle'], $const);
  138. }
  139. if($isAsynchronous)
  140. curl_multi_remove_handle($this->mc, $done['handle']);
  141. curl_close($done['handle']);
  142. }
  143. private function startTimer($key)
  144. {
  145. self::$timers[$key]['start'] = microtime(true);
  146. }
  147. private function stopTimer($key, $done)
  148. {
  149. self::$timers[$key]['end'] = microtime(true);
  150. self::$timers[$key]['api'] = curl_getinfo($done['handle'], CURLINFO_EFFECTIVE_URL);
  151. self::$timers[$key]['time'] = curl_getinfo($done['handle'], CURLINFO_TOTAL_TIME);
  152. self::$timers[$key]['code'] = curl_getinfo($done['handle'], CURLINFO_HTTP_CODE);
  153. }
  154. static function getInstance()
  155. {
  156. if(self::$inst == null)
  157. {
  158. self::$singleton = 1;
  159. self::$inst = new EpiCurl();
  160. }
  161. return self::$inst;
  162. }
  163. }
  164. class EpiCurlManager
  165. {
  166. private $key;
  167. private $epiCurl;
  168. public function __construct($key)
  169. {
  170. $this->key = $key;
  171. $this->epiCurl = EpiCurl::getInstance();
  172. }
  173. public function __get($name)
  174. {
  175. $responses = $this->epiCurl->getResult($this->key);
  176. return isset($responses[$name]) ? $responses[$name] : null;
  177. }
  178. public function __isset($name)
  179. {
  180. $val = self::__get($name);
  181. return empty($val);
  182. }
  183. }
  184. /*
  185. * Credits:
  186. * - (1) Alistair pointed out that curl_multi_add_handle can return CURLM_CALL_MULTI_PERFORM on success.
  187. */