HttpSocketLib.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <?php
  2. App::uses('HttpSocket', 'Network/Http');
  3. App::uses('CurlLib', 'Tools.Lib');
  4. /**
  5. * Wrapper for curl, php or file_get_contents with some fixes or improvements:
  6. * - All 2xx OK status codes will return the proper result
  7. * - Response will be properly utf8 encoded on WINDOWS, as well
  8. * - Timeout is reduced to 5 by default and can easily be adjusted
  9. * - file_get_contents wrapper is fixed for HTTP1.1 to default to "Connection: close"
  10. * to avoid leaving the connection open
  11. * - Caching possibilities included
  12. * - Auto-Fallback if curl is not available
  13. *
  14. * TODO: throw exceptions instead of error stuff here
  15. *
  16. * @author Mark Scherer
  17. * @license MIT
  18. * @cakephp 2.x
  19. * 2011-10-14 ms
  20. */
  21. class HttpSocketLib {
  22. // First tries with curl, then cake, then php
  23. public $use = array('curl' => true, 'cake'=> true, 'php' => true);
  24. public $debug = null;
  25. public $timeout = 5;
  26. public $cacheUsed = null;
  27. public $error = array();
  28. public $allowRedirects = array(301);
  29. public function __construct($use = array()) {
  30. if (is_array($use)) {
  31. foreach ($use as $key => $value) {
  32. if (array_key_exists($key, $this->use)) {
  33. $this->use[$key] = $value;
  34. }
  35. }
  36. } elseif (array_key_exists($use, $this->use)) {
  37. $this->use[$use] = true;
  38. if ($use === 'cake') {
  39. $this->use['curl'] = false;
  40. } elseif ($use === 'php') {
  41. $this->use['curl'] = $this->use['cake'] = false;
  42. }
  43. }
  44. }
  45. /**
  46. * @param string $error
  47. */
  48. public function setError($error) {
  49. if (empty($error)) {
  50. return;
  51. }
  52. $this->error[] = $error;
  53. }
  54. /**
  55. * @return string
  56. */
  57. public function error($asString = true, $separator = ', ') {
  58. return implode(', ', $this->error);
  59. }
  60. public function reset() {
  61. $this->error = array();
  62. $this->debug = null;
  63. }
  64. /**
  65. * fetches url with curl if available
  66. * fallbacks: cake and php
  67. * note: expects url with json encoded content
  68. *
  69. * @param string $url
  70. * @param array $options
  71. * @return string Response or false on failure
  72. */
  73. public function fetch($url, $options = array()) {
  74. if (!is_array($options)) {
  75. $options = array('agent'=>$options);
  76. }
  77. $defaults = array(
  78. 'agent' => 'cakephp http socket lib',
  79. 'cache' => false,
  80. 'clearCache' => false,
  81. 'use' => $this->use,
  82. 'timeout' => $this->timeout,
  83. );
  84. $options = array_merge($defaults, $options);
  85. # cached?
  86. if ($options['cache']) {
  87. $cacheName = md5($url);
  88. $cacheConfig = $options['cache'] === true ? null: $options['cache'];
  89. $cacheConfig = !Cache::isInitialized($cacheConfig) ? null : $cacheConfig;
  90. if ($options['clearCache']) {
  91. Cache::delete('http_'.$cacheName, $cacheConfig);
  92. } elseif (($res = Cache::read('http_'.$cacheName, $cacheConfig)) !== false && $res !== null) {
  93. $this->cacheUsed = true;
  94. return $res;
  95. }
  96. }
  97. $res = $this->_fetch($url, $options);
  98. if ($options['cache']) {
  99. Cache::write('http_'.$cacheName, $res, $cacheConfig);
  100. }
  101. return $res;
  102. }
  103. /**
  104. * @param string $url
  105. * @param array $options
  106. * @return string Response or false on failure
  107. */
  108. public function _fetch($url, $options) {
  109. $allowedCodes = array_merge($this->allowRedirects, array(200, 201, 202, 203, 204, 205, 206));
  110. if ($options['use']['curl'] && function_exists('curl_init')) {
  111. $this->debug = 'curl';
  112. $Ch = new CurlLib();
  113. $Ch->setUserAgent($options['agent']);
  114. $data = $Ch->get($url);
  115. $response = $data[0];
  116. $statusCode = $data[1]['http_code'];
  117. if (!in_array($statusCode, $allowedCodes)) {
  118. $this->setError('Error '.$statusCode);
  119. return false;
  120. }
  121. $response = $this->_assertEncoding($response);
  122. return $response;
  123. }
  124. if ($options['use']['cake']) {
  125. $this->debug = 'cake';
  126. $HttpSocket = new HttpSocket(array('timeout' => $options['timeout']));
  127. $response = $HttpSocket->get($url);
  128. if (!in_array($response->code, $allowedCodes)) {
  129. return false;
  130. }
  131. $response = $this->_assertEncoding($response);
  132. return $response;
  133. }
  134. if ($options['use']['php']) {
  135. $this->debug = 'php';
  136. $opts = array(
  137. 'http' => array(
  138. 'method' => 'GET',
  139. 'header' => array('Connection: close'),
  140. 'timeout' => $options['timeout']
  141. )
  142. );
  143. if (isset($options['http'])) {
  144. $opts['http'] = array_merge($opts['http'], $options['http']);
  145. }
  146. if (is_array($opts['http']['header'])) {
  147. $opts['http']['header'] = implode(PHP_EOL, $opts['http']['header']);
  148. }
  149. $context = stream_context_create($opts);
  150. $response = file_get_contents($url, false, $context);
  151. if (!isset($http_response_header)) {
  152. return false;
  153. }
  154. preg_match('/^HTTP.*\s([0-9]{3})/', $http_response_header[0], $matches);
  155. $statusCode = (int)$matches[1];
  156. if (!in_array($statusCode, $allowedCodes)) {
  157. return false;
  158. }
  159. $response = $this->_assertEncoding($response);
  160. return $response;
  161. }
  162. throw new CakeException('no protocol given');
  163. }
  164. /**
  165. * It seems all three methods have encoding issues if not run through this method
  166. *
  167. * @param string $response
  168. * @param string Correctly encoded response
  169. */
  170. protected function _assertEncoding($response) {
  171. if (!WINDOWS) {
  172. return $response;
  173. }
  174. $x = mb_detect_encoding($response, 'auto', true);
  175. if ($x !== 'UTF-8') {
  176. $response = iconv(null, "utf-8", $response);
  177. }
  178. return $response;
  179. }
  180. }