Http.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <?php
  2. /*
  3. * This file is part of the overtrue/wechat.
  4. *
  5. * (c) overtrue <i@overtrue.me>
  6. *
  7. * This source file is subject to the MIT license that is bundled
  8. * with this source code in the file LICENSE.
  9. */
  10. /**
  11. * Http.php.
  12. *
  13. * This file is part of the wechat-components.
  14. *
  15. * (c) overtrue <i@overtrue.me>
  16. *
  17. * This source file is subject to the MIT license that is bundled
  18. * with this source code in the file LICENSE.
  19. */
  20. namespace EasyWeChat\Core;
  21. use EasyWeChat\Core\Exceptions\HttpException;
  22. use EasyWeChat\Support\Log;
  23. use GuzzleHttp\Client as HttpClient;
  24. use GuzzleHttp\HandlerStack;
  25. use Psr\Http\Message\ResponseInterface;
  26. /**
  27. * Class Http.
  28. */
  29. class Http
  30. {
  31. /**
  32. * Used to identify handler defined by client code
  33. * Maybe useful in the future.
  34. */
  35. const USER_DEFINED_HANDLER = 'userDefined';
  36. /**
  37. * Http client.
  38. *
  39. * @var HttpClient
  40. */
  41. protected $client;
  42. /**
  43. * The middlewares.
  44. *
  45. * @var array
  46. */
  47. protected $middlewares = [];
  48. /**
  49. * @var array
  50. */
  51. protected static $globals = [
  52. 'curl' => [
  53. CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
  54. ],
  55. ];
  56. /**
  57. * Guzzle client default settings.
  58. *
  59. * @var array
  60. */
  61. protected static $defaults = [];
  62. /**
  63. * Set guzzle default settings.
  64. *
  65. * @param array $defaults
  66. */
  67. public static function setDefaultOptions($defaults = [])
  68. {
  69. self::$defaults = array_merge(self::$globals, $defaults);
  70. }
  71. /**
  72. * Return current guzzle default settings.
  73. *
  74. * @return array
  75. */
  76. public static function getDefaultOptions()
  77. {
  78. return self::$defaults;
  79. }
  80. /**
  81. * GET request.
  82. *
  83. * @param string $url
  84. * @param array $options
  85. *
  86. * @return ResponseInterface
  87. *
  88. * @throws HttpException
  89. */
  90. public function get($url, array $options = [])
  91. {
  92. return $this->request($url, 'GET', ['query' => $options]);
  93. }
  94. /**
  95. * POST request.
  96. *
  97. * @param string $url
  98. * @param array|string $options
  99. *
  100. * @return ResponseInterface
  101. *
  102. * @throws HttpException
  103. */
  104. public function post($url, $options = [])
  105. {
  106. $key = is_array($options) ? 'form_params' : 'body';
  107. return $this->request($url, 'POST', [$key => $options]);
  108. }
  109. /**
  110. * JSON request.
  111. *
  112. * @param string $url
  113. * @param string|array $options
  114. * @param array $queries
  115. * @param int $encodeOption
  116. *
  117. * @return ResponseInterface
  118. *
  119. * @throws HttpException
  120. */
  121. public function json($url, $options = [], $encodeOption = JSON_UNESCAPED_UNICODE, $queries = [])
  122. {
  123. is_array($options) && $options = json_encode($options, $encodeOption);
  124. return $this->request($url, 'POST', ['query' => $queries, 'body' => $options, 'headers' => ['content-type' => 'application/json']]);
  125. }
  126. /**
  127. * Upload file.
  128. *
  129. * @param string $url
  130. * @param array $files
  131. * @param array $form
  132. *
  133. * @return ResponseInterface
  134. *
  135. * @throws HttpException
  136. */
  137. public function upload($url, array $files = [], array $form = [], array $queries = [])
  138. {
  139. $multipart = [];
  140. foreach ($files as $name => $path) {
  141. $multipart[] = [
  142. 'name' => $name,
  143. 'contents' => fopen($path, 'r'),
  144. ];
  145. }
  146. foreach ($form as $name => $contents) {
  147. $multipart[] = compact('name', 'contents');
  148. }
  149. return $this->request($url, 'POST', ['query' => $queries, 'multipart' => $multipart]);
  150. }
  151. /**
  152. * Set GuzzleHttp\Client.
  153. *
  154. * @param \GuzzleHttp\Client $client
  155. *
  156. * @return Http
  157. */
  158. public function setClient(HttpClient $client)
  159. {
  160. $this->client = $client;
  161. return $this;
  162. }
  163. /**
  164. * Return GuzzleHttp\Client instance.
  165. *
  166. * @return \GuzzleHttp\Client
  167. */
  168. public function getClient()
  169. {
  170. if (!($this->client instanceof HttpClient)) {
  171. $this->client = new HttpClient();
  172. }
  173. return $this->client;
  174. }
  175. /**
  176. * Add a middleware.
  177. *
  178. * @param callable $middleware
  179. *
  180. * @return $this
  181. */
  182. public function addMiddleware(callable $middleware)
  183. {
  184. array_push($this->middlewares, $middleware);
  185. return $this;
  186. }
  187. /**
  188. * Return all middlewares.
  189. *
  190. * @return array
  191. */
  192. public function getMiddlewares()
  193. {
  194. return $this->middlewares;
  195. }
  196. /**
  197. * Make a request.
  198. *
  199. * @param string $url
  200. * @param string $method
  201. * @param array $options
  202. *
  203. * @return ResponseInterface
  204. *
  205. * @throws HttpException
  206. */
  207. public function request($url, $method = 'GET', $options = [])
  208. {
  209. $method = strtoupper($method);
  210. $options = array_merge(self::$defaults, $options);
  211. Log::debug('Client Request:', compact('url', 'method', 'options'));
  212. $options['handler'] = $this->getHandler();
  213. $response = $this->getClient()->request($method, $url, $options);
  214. Log::debug('API response:', [
  215. 'Status' => $response->getStatusCode(),
  216. 'Reason' => $response->getReasonPhrase(),
  217. 'Headers' => $response->getHeaders(),
  218. 'Body' => strval($response->getBody()),
  219. ]);
  220. return $response;
  221. }
  222. /**
  223. * @param \Psr\Http\Message\ResponseInterface|string $body
  224. *
  225. * @return mixed
  226. *
  227. * @throws \EasyWeChat\Core\Exceptions\HttpException
  228. */
  229. public function parseJSON($body)
  230. {
  231. if ($body instanceof ResponseInterface) {
  232. $body = mb_convert_encoding($body->getBody(), 'UTF-8');
  233. }
  234. // XXX: json maybe contains special chars. So, let's FUCK the WeChat API developers ...
  235. $body = $this->fuckTheWeChatInvalidJSON($body);
  236. if (empty($body)) {
  237. return false;
  238. }
  239. $contents = json_decode($body, true, 512, JSON_BIGINT_AS_STRING);
  240. Log::debug('API response decoded:', compact('contents'));
  241. if (JSON_ERROR_NONE !== json_last_error()) {
  242. throw new HttpException('Failed to parse JSON: '.json_last_error_msg());
  243. }
  244. return $contents;
  245. }
  246. /**
  247. * Filter the invalid JSON string.
  248. *
  249. * @param \Psr\Http\Message\StreamInterface|string $invalidJSON
  250. *
  251. * @return string
  252. */
  253. protected function fuckTheWeChatInvalidJSON($invalidJSON)
  254. {
  255. return preg_replace('/[\x00-\x1F\x80-\x9F]/u', '', trim($invalidJSON));
  256. }
  257. /**
  258. * Build a handler.
  259. *
  260. * @return HandlerStack
  261. */
  262. protected function getHandler()
  263. {
  264. $stack = HandlerStack::create();
  265. foreach ($this->middlewares as $middleware) {
  266. $stack->push($middleware);
  267. }
  268. if (isset(static::$defaults['handler']) && is_callable(static::$defaults['handler'])) {
  269. $stack->push(static::$defaults['handler'], self::USER_DEFINED_HANDLER);
  270. }
  271. return $stack;
  272. }
  273. }