fetch.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <?php
  2. /**
  3. * This file contain fetch class, which is used to communicate with NTFY.sh
  4. * REST api. It makes that HTTP request using buildin CURL extension is
  5. * much easier, and looks better in high level code.
  6. *
  7. * @package phpnotify
  8. * @author Cixo (Cixo Electronic)
  9. */
  10. namespace phpnotify;
  11. use \TypeError as TypeError;
  12. use \RuntimeException as RuntimeException;
  13. use \CurlHandle as CurlHandle;
  14. use \curl_init as curl_init;
  15. use \curl_setopt as curl_setopt;
  16. use \curl_close as curl_close;
  17. use \curl_exec as curl_exec;
  18. use \curl_error as curl_error;
  19. use \curl_errno as curl_errno;
  20. require('response.php');
  21. /**
  22. * This class is responsible for downloading data from the server. It uses
  23. * buildin CURL extension to make HTTP request. It not parsing response
  24. * directly, but use {@link response}
  25. */
  26. class fetch {
  27. /**
  28. * This store content of the requst body.
  29. * @var ?string
  30. */
  31. private ?string $content;
  32. /**
  33. * This store type of content.
  34. * @var ?string
  35. */
  36. private ?string $content_type;
  37. /**
  38. * This store method of the request.
  39. * @var string
  40. */
  41. private string $method;
  42. /**
  43. * This store url to fetch.
  44. * @var string
  45. */
  46. private string $url;
  47. /**
  48. * This store headers of the request.
  49. * @var array
  50. */
  51. private array $headers;
  52. /**
  53. * This create new fetch request. It is alias of constructor.
  54. *
  55. * @link fetch::__construct()
  56. * @static
  57. * @param string $url URL to fetch.
  58. * @return fetch New fetch object.
  59. */
  60. public static function create(string $url): object {
  61. return new self($url);
  62. }
  63. /**
  64. * This initialize new fetch request by given URL. Default method for
  65. * the request is GET. If do not want to use new, see create.
  66. *
  67. * @link fetch::create()
  68. * @param string $url URL to work with.
  69. */
  70. public function __construct(string $url) {
  71. $this->content = null;
  72. $this->content_type = null;
  73. $this->method = 'GET';
  74. $this->headers = array();
  75. $this->url = $url;
  76. }
  77. /**
  78. * This function could be use send array in the fetch request. Be careful
  79. * because this function verify that method of the request is not GET or
  80. * HEAD, which can not contain body. Property method must be set before.
  81. *
  82. * @param array $content Content which must be send.
  83. * @return fetch Itself to chain load.
  84. */
  85. public function send_array(array $content): object {
  86. $content = json_encode($content);
  87. $type = 'application/json';
  88. $this->send_raw($content, $type);
  89. return $this;
  90. }
  91. /**
  92. * This function is similar to {@link fetch::send_array()}, but work on
  93. * raw string data. Also content type must be set. Be careful, because
  94. * it check that method of the request is not GET or HEAD, which can
  95. * not contain body content.
  96. *
  97. * @param string $content Content of the body to send.
  98. * @param string $type Type of the content, like "text/plain".
  99. * @return fetch Self to chan loading.
  100. */
  101. public function send_raw(string $content, string $type): object {
  102. if ($this->method === 'GET' or $this->method === 'HEAD') {
  103. throw new TypeError('GET or HEAD request can not contain body.');
  104. }
  105. $this->content = $content;
  106. $this->content_type = $type;
  107. return $this;
  108. }
  109. /**
  110. * That add new header to the request. Name of the header must be full
  111. * featured header name, like 'Cache-Control'. It also had been validated
  112. * and when name had been blacklisted, it raise RuntimeException. It also
  113. * raise RuntimeException when name has any white chars. Content had been
  114. * validating, and it could not have new lines. Name and content would be
  115. * trimed before processing.
  116. *
  117. * Blacklisted headers:
  118. * Content-Type, Content-Length
  119. *
  120. * @param string $name Name of the header.
  121. * @param string $content Content of the header.
  122. * @return fetch Self to chain processing.
  123. */
  124. public function add_header(string $name, string $content): object {
  125. $name = trim($name);
  126. $content = trim($content);
  127. if (strpos($name, ' ') !== false) {
  128. throw new RuntimeException(
  129. 'Header "'.$name.'" contains white char.'
  130. );
  131. }
  132. $check_content = strpos($content, ' ') !== false;
  133. $check_content = $check_content || strpos($content, "\r") !== false;
  134. $check_content = $check_content || strpos($content, "\n") !== false;
  135. if ($check_content) {
  136. throw new RuntimeException(
  137. 'Header content "'.$content.'" contains invalid white char.'
  138. );
  139. }
  140. if ($name === 'Content-Type') {
  141. throw new RuntimeException('Content-Type header is automatic.');
  142. }
  143. if ($name === 'Content-Lenght') {
  144. throw new RuntimeException('Content-Lenght header is automatic.');
  145. }
  146. $header = $name.': '.$content;
  147. array_push($this->headers, $header);
  148. return $this;
  149. }
  150. /**
  151. * This function set method of the request. Be careful, because it check
  152. * that any content is not set, and when content is not null, but trying
  153. * to set GET or HEAD method, it throw TypeError.
  154. *
  155. * @param string $name Name of the method.
  156. * @return fetch Self to chain loading.
  157. */
  158. public function set_method(string $name): object {
  159. if ($this->content === null) {
  160. $this->method = $name;
  161. return $this;
  162. }
  163. if ($name === 'GET' || $name === 'HEAD') {
  164. throw new TypeError('GET or HEAD request can not contain body.');
  165. }
  166. $this->method = $name;
  167. return $this;
  168. }
  169. /**
  170. * That function make request to the server, from fetch config, which
  171. * had been config previously. It return response in parsed form, as
  172. * instance of response class. Be careful, it raise an RuntimeException
  173. * when server does not response correctly.
  174. *
  175. * @return response Response from the server.
  176. */
  177. public function request(): response {
  178. $headers = $this->headers;
  179. $request = curl_init($this->url);
  180. curl_setopt($request, CURLOPT_RETURNTRANSFER, true);
  181. curl_setopt($request, CURLOPT_FAILONERROR, true);
  182. curl_setopt($request, CURLOPT_HEADER, true);
  183. curl_setopt($request,CURLOPT_CUSTOMREQUEST, $this->method);
  184. if ($this->content !== null) {
  185. array_push($headers, 'Content-Type: '.$this->content_type);
  186. curl_setopt($request, CURLOPT_POSTFIELDS, $this->content);
  187. }
  188. curl_setopt($request, CURLOPT_HTTPHEADER, $headers);
  189. $result = curl_exec($request);
  190. $error = curl_error($request);
  191. $error_number = curl_errno($request);
  192. curl_close($request);
  193. if ($result !== false and $error_number === 0) {
  194. return new response($result);
  195. }
  196. throw new RuntimeException(
  197. 'Can not fetch request. Error code '
  198. .strval($error_number)
  199. .': "'.$error.'".'
  200. );
  201. }
  202. }