PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/api/rest.php

https://gitlab.com/php-kore/php-kore
PHP | 407 lines | 196 code | 52 blank | 159 comment | 28 complexity | f1721f9331842ddec9fe5bf353431dae MD5 | raw file
  1. <?php
  2. /**
  3. * This class allow easy usage of a service via a REST API.
  4. *
  5. * For now, the implemantation rely on PHP HTTP streams only.
  6. *
  7. * @package API_REST
  8. */
  9. class kore_api_rest
  10. {
  11. private $_scheme = 'http';
  12. private $_host = 'localhost';
  13. private $_port;
  14. private $_path = '';
  15. protected $_dataContentType = 'application/x-www-form-urlencoded';
  16. private $_contextOpts = [ 'http' => [
  17. 'ignore_errors' => true,
  18. 'protocol_version' => '1.1' ]];
  19. private $_headers = [
  20. 'Accept-Encoding' => 'gzip',
  21. 'Connection' => 'close' ];
  22. /**
  23. * Init a kore_api_rest object, specifying the baseUrl of all futures
  24. * *relatives* queries.
  25. *
  26. * @param $baseUrl
  27. */
  28. public function __construct($baseUrl)
  29. {
  30. $u = parse_url($baseUrl);
  31. if (isset($u['scheme']))
  32. $this->_scheme = strtolower($u['scheme']);
  33. if (isset($u['host']))
  34. $this->_host = $u['host'];
  35. if (isset($u['port']))
  36. $this->_port = (int) $u['port'];
  37. elseif ($this->_scheme === 'https')
  38. $this->_port = 443;
  39. else
  40. $this->_port = 80;
  41. if (isset($u['path']))
  42. $this->_path = $u['path'];
  43. }
  44. /**
  45. * Define a specific HTTP header to add to all queries
  46. *
  47. * @param $name
  48. * @param $value
  49. */
  50. public function setHeader($name, $value)
  51. {
  52. $this->_headers[$name] = $value;
  53. }
  54. /**
  55. * Get the value of an HTTP header, which is added to all queries
  56. *
  57. * @see kore_api_rest::setHeader()
  58. * @param $name
  59. */
  60. public function getHeader($name)
  61. {
  62. return @$this->_headers[$name];
  63. }
  64. /**
  65. * Remove an HTTP header, previously added throught setHeader()
  66. *
  67. * @see kore_api_rest::setHeader()
  68. * @param $name
  69. */
  70. public function delHeader($name)
  71. {
  72. unset($this->_headers[$name]);
  73. }
  74. /**
  75. * Use HTTP authentication via "basic" method.
  76. *
  77. * @param $login
  78. * @param $password
  79. */
  80. public function setHTTPBasicAuth($login, $password)
  81. {
  82. $this->setHeader('Authorization', 'Basic ' .
  83. base64_encode("$login:$password"));
  84. }
  85. /**
  86. * Disable HTTP authentication.
  87. *
  88. */
  89. public function delHTTPAuth()
  90. {
  91. $this->delHeader('Authorization');
  92. }
  93. /**
  94. * Build defaults context options
  95. *
  96. * @param $method
  97. * @param $miscHeaders
  98. * @return array
  99. */
  100. protected function _getContextOpts($method, array $miscHeaders = null)
  101. {
  102. $opts = $this->_contextOpts;
  103. $opts['http']['method'] = $method;
  104. $headers = $this->_headers;
  105. if ($miscHeaders)
  106. $headers = $miscHeaders + $headers;
  107. if ($headers){
  108. if (!isset($opts['http']['header']))
  109. $opts['http']['header'] = '';
  110. foreach ($headers as $name => $value)
  111. $opts['http']['header'] .= "{$name}: $value\r\n";
  112. }
  113. return $opts;
  114. }
  115. /**
  116. * Create a stream context based on given options.
  117. *
  118. * @param array $contextOpts
  119. * @return resource
  120. */
  121. protected function _createContext(array $contextOpts)
  122. {
  123. return stream_context_create($contextOpts);
  124. }
  125. /**
  126. * Process the query.
  127. *
  128. * @param string $url
  129. * @param array $contextOpts
  130. * @return kore_api_restResult
  131. * @throws kore_api_restException
  132. */
  133. protected function _query($url, array $contextOpts)
  134. {
  135. $stream = fopen($url, 'r', false, $this->_createContext($contextOpts));
  136. if (!$stream)
  137. throw new kore_api_restException("error while querying $url");
  138. $result = new kore_api_restResult();
  139. $metas = stream_get_meta_data($stream);
  140. foreach ($metas['wrapper_data'] as $header){
  141. if (preg_match('#^HTTP/[0-9.]+ ([0-9]{3})(?:\\s+(.*))?$#', $header, $m)){
  142. $result->code = (int) $m[1];
  143. if (isset($m[2]))
  144. $result->codeMessage = $m[2];
  145. else
  146. $result->codeMessage = null;
  147. } elseif (preg_match('#^([a-zA-Z0-9_\\-]+):\\s*(.*)$#', $header, $m)){
  148. $result->headers[strtolower($m[1])] = $m[2];
  149. }
  150. }
  151. if (!$metas['eof'])
  152. $result->contents = stream_get_contents($stream);
  153. if ($result->code >= 400)
  154. $this->_handleHTTPError($result);
  155. return $this->_fetchContents($result);
  156. }
  157. /**
  158. * Handle HTTP errors.
  159. *
  160. * @param kore_api_restResult $result
  161. * @throws kore_api_restException
  162. */
  163. protected function _handleHTTPError(kore_api_restResult $result)
  164. {
  165. if ($result->codeMessage)
  166. $message = $result->codeMessage;
  167. else
  168. $message = "http code #".$result->code;
  169. throw new kore_api_restException("REST: ".$message, $result->code);
  170. }
  171. /**
  172. * Fetch the result, and transform/convert it if needed.
  173. *
  174. * @param kore_api_restResult $result
  175. * @return kore_api_restResult
  176. */
  177. protected function _fetchContents(kore_api_restResult $result)
  178. {
  179. /*
  180. * If transfert was "encoded" we need to decode it.
  181. *
  182. * For now, we only handle GZIP decompression.
  183. */
  184. if (isset($result->headers['content-encoding'])){
  185. if ($result->headers['content-encoding'] === 'gzip')
  186. $result->contents = gzdecode($result->contents);
  187. }
  188. return $result;
  189. }
  190. /**
  191. * Create the full URI, based on the current $baseUrl, requested relative
  192. * $url and any given params.
  193. *
  194. * @param string $url
  195. * @param array $additionnalParams
  196. * @return string
  197. */
  198. protected function _buildUrl($url, array $additionnalParams = null)
  199. {
  200. if (substr($url, 0, 1) === '/'){
  201. $baseUrl = $this->_scheme.'://'.$this->_host;
  202. if ($this->_scheme === 'http'){
  203. if($this->_port != 80) $baseUrl .= ':'.$this->_port;
  204. } elseif ($this->_scheme === 'https'){
  205. if($this->_port != 443) $baseUrl .= ':'.$this->_port;
  206. } else {
  207. $baseUrl .= ':'.$this->_port;
  208. }
  209. $url = $baseUrl . $this->_path . $url;
  210. }
  211. if ($additionnalParams){
  212. if (strpos($url, '?') !== false)
  213. $url .= '&';
  214. else
  215. $url .= '?';
  216. $url .= http_build_query($additionnalParams, '',
  217. ini_get('arg_separator.output'), PHP_QUERY_RFC1738);
  218. }
  219. return $url;
  220. }
  221. /**
  222. * Encode uploaded data from POST and PUT queries.
  223. *
  224. * @param mixed $data
  225. * @return string
  226. */
  227. protected function _encodeData($data)
  228. {
  229. if (is_array($data))
  230. $data = http_build_query($data, '', '&', PHP_QUERY_RFC1738);
  231. return $data;
  232. }
  233. /**
  234. * Throw a HEAD query.
  235. *
  236. * @param string $url
  237. * @param array $data
  238. * @param array $miscHeaders
  239. * @return kore_api_restResult
  240. * @throws kore_api_restException
  241. */
  242. public function head($url, array $data = null, array $miscHeaders = null)
  243. {
  244. $url = $this->_buildUrl($url, $data);
  245. $opts = $this->_getContextOpts('HEAD', $miscHeaders);
  246. return $this->_query($url, $opts);
  247. }
  248. /**
  249. * Throw a GET query.
  250. *
  251. * @param string $url
  252. * @param array $data
  253. * @param array $miscHeaders
  254. * @return kore_api_restResult
  255. * @throws kore_api_restException
  256. */
  257. public function get($url, array $data = null, array $miscHeaders = null)
  258. {
  259. $url = $this->_buildUrl($url, $data);
  260. $opts = $this->_getContextOpts('GET', $miscHeaders);
  261. return $this->_query($url, $opts);
  262. }
  263. /**
  264. * Inject uploaded data in the given stream context
  265. *
  266. * @param mixed $data
  267. * @param array $contextOpts
  268. */
  269. public function _addContent($data, array &$contextOpts)
  270. {
  271. $data = $this->_encodeData($data);
  272. if ($data)
  273. $contextOpts['http']['content'] = $data;
  274. if (!isset($contextOpts['http']['header']))
  275. $contextOpts['http']['header'] = "Content-type: {$this->_dataContentType}\r\n";
  276. elseif ((stripos($contextOpts['http']['header'], 'Content-type:') === false))
  277. $contextOpts['http']['header'] .= "Content-type: {$this->_dataContentType}\r\n";
  278. }
  279. /**
  280. * Throw a POST query.
  281. *
  282. * @param string $url
  283. * @param mixed $data
  284. * @param array $miscHeaders
  285. * @return kore_api_restResult
  286. * @throws kore_api_restException
  287. */
  288. public function post($url, $data = null, array $miscHeaders = null)
  289. {
  290. $url = $this->_buildUrl($url);
  291. $opts = $this->_getContextOpts('POST', $miscHeaders);
  292. $this->_addContent($data, $opts);
  293. return $this->_query($url, $opts);
  294. }
  295. /**
  296. * Throw a PUT query.
  297. *
  298. * @param string $url
  299. * @param mixed $data
  300. * @param array $miscHeaders
  301. * @return kore_api_restResult
  302. * @throws kore_api_restException
  303. */
  304. public function put($url, $data = null, array $miscHeaders = null)
  305. {
  306. $url = $this->_buildUrl($url);
  307. $opts = $this->_getContextOpts('PUT', $miscHeaders);
  308. $this->_addContent($data, $opts);
  309. return $this->_query($url, $opts);
  310. }
  311. /**
  312. * Throw a DELETE query.
  313. *
  314. * @param string $url
  315. * @param array $data
  316. * @param array $miscHeaders
  317. * @return kore_api_restResult
  318. * @throws kore_api_restException
  319. */
  320. public function delete($url, array $data = null, array $miscHeaders = null)
  321. {
  322. $url = $this->_buildUrl($url, $data);
  323. $opts = $this->_getContextOpts('DELETE', $miscHeaders);
  324. return $this->_query($url, $opts);
  325. }
  326. }
  327. /**
  328. * Exceptions throws as a result of HTTP requests.
  329. *
  330. * @package API_REST
  331. */
  332. class kore_api_restException extends exception
  333. {
  334. }
  335. /**
  336. * Class which contains result of HTTP requests.
  337. *
  338. * @package API_REST
  339. */
  340. class kore_api_restResult
  341. {
  342. public $code = 599;
  343. public $codeMessage = 'unable to parse server answer';
  344. public $headers = array();
  345. public $contents = '';
  346. /**
  347. * Shortcut to easily access to the contents.
  348. *
  349. * @return string
  350. */
  351. public function __toString()
  352. {
  353. return (string) $this->contents;
  354. }
  355. }