BaseClient.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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. namespace EasyWeChat\Kernel;
  11. use EasyWeChat\Kernel\Contracts\AccessTokenInterface;
  12. use EasyWeChat\Kernel\Http\Response;
  13. use EasyWeChat\Kernel\Traits\HasHttpRequests;
  14. use GuzzleHttp\MessageFormatter;
  15. use GuzzleHttp\Middleware;
  16. use Psr\Http\Message\RequestInterface;
  17. use Psr\Http\Message\ResponseInterface;
  18. use Psr\Log\LogLevel;
  19. /**
  20. * Class BaseClient.
  21. *
  22. * @author overtrue <i@overtrue.me>
  23. */
  24. class BaseClient
  25. {
  26. use HasHttpRequests { request as performRequest; }
  27. /**
  28. * @var \EasyWeChat\Kernel\ServiceContainer
  29. */
  30. protected $app;
  31. /**
  32. * @var \EasyWeChat\Kernel\Contracts\AccessTokenInterface
  33. */
  34. protected $accessToken;
  35. /**
  36. * @var string
  37. */
  38. protected $baseUri;
  39. /**
  40. * BaseClient constructor.
  41. *
  42. * @param \EasyWeChat\Kernel\ServiceContainer $app
  43. */
  44. public function __construct(ServiceContainer $app, AccessTokenInterface $accessToken = null)
  45. {
  46. $this->app = $app;
  47. $this->accessToken = $accessToken ?? $this->app['access_token'];
  48. }
  49. /**
  50. * GET request.
  51. *
  52. * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
  53. *
  54. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  55. * @throws \GuzzleHttp\Exception\GuzzleException
  56. */
  57. public function httpGet(string $url, array $query = [])
  58. {
  59. return $this->request($url, 'GET', ['query' => $query]);
  60. }
  61. /**
  62. * POST request.
  63. *
  64. * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
  65. *
  66. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  67. * @throws \GuzzleHttp\Exception\GuzzleException
  68. */
  69. public function httpPost(string $url, array $data = [])
  70. {
  71. return $this->request($url, 'POST', ['form_params' => $data]);
  72. }
  73. /**
  74. * JSON request.
  75. *
  76. * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
  77. *
  78. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  79. * @throws \GuzzleHttp\Exception\GuzzleException
  80. */
  81. public function httpPostJson(string $url, array $data = [], array $query = [])
  82. {
  83. return $this->request($url, 'POST', ['query' => $query, 'json' => $data]);
  84. }
  85. /**
  86. * Upload file.
  87. *
  88. * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
  89. *
  90. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  91. * @throws \GuzzleHttp\Exception\GuzzleException
  92. */
  93. public function httpUpload(string $url, array $files = [], array $form = [], array $query = [])
  94. {
  95. $multipart = [];
  96. foreach ($files as $name => $path) {
  97. $multipart[] = [
  98. 'name' => $name,
  99. 'contents' => fopen($path, 'r'),
  100. ];
  101. }
  102. foreach ($form as $name => $contents) {
  103. $multipart[] = compact('name', 'contents');
  104. }
  105. return $this->request($url, 'POST', ['query' => $query, 'multipart' => $multipart, 'connect_timeout' => 30, 'timeout' => 30, 'read_timeout' => 30]);
  106. }
  107. public function getAccessToken(): AccessTokenInterface
  108. {
  109. return $this->accessToken;
  110. }
  111. /**
  112. * @return $this
  113. */
  114. public function setAccessToken(AccessTokenInterface $accessToken)
  115. {
  116. $this->accessToken = $accessToken;
  117. return $this;
  118. }
  119. /**
  120. * @param bool $returnRaw
  121. *
  122. * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
  123. *
  124. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  125. * @throws \GuzzleHttp\Exception\GuzzleException
  126. */
  127. public function request(string $url, string $method = 'GET', array $options = [], $returnRaw = false)
  128. {
  129. if (empty($this->middlewares)) {
  130. $this->registerHttpMiddlewares();
  131. }
  132. $response = $this->performRequest($url, $method, $options);
  133. $this->app->events->dispatch(new Events\HttpResponseCreated($response));
  134. return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
  135. }
  136. /**
  137. * @return \EasyWeChat\Kernel\Http\Response
  138. *
  139. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  140. * @throws \GuzzleHttp\Exception\GuzzleException
  141. */
  142. public function requestRaw(string $url, string $method = 'GET', array $options = [])
  143. {
  144. return Response::buildFromPsrResponse($this->request($url, $method, $options, true));
  145. }
  146. /**
  147. * Register Guzzle middlewares.
  148. */
  149. protected function registerHttpMiddlewares()
  150. {
  151. // retry
  152. $this->pushMiddleware($this->retryMiddleware(), 'retry');
  153. // access token
  154. $this->pushMiddleware($this->accessTokenMiddleware(), 'access_token');
  155. // log
  156. $this->pushMiddleware($this->logMiddleware(), 'log');
  157. }
  158. /**
  159. * Attache access token to request query.
  160. *
  161. * @return \Closure
  162. */
  163. protected function accessTokenMiddleware()
  164. {
  165. return function (callable $handler) {
  166. return function (RequestInterface $request, array $options) use ($handler) {
  167. if ($this->accessToken) {
  168. $request = $this->accessToken->applyToRequest($request, $options);
  169. }
  170. return $handler($request, $options);
  171. };
  172. };
  173. }
  174. /**
  175. * Log the request.
  176. *
  177. * @return \Closure
  178. */
  179. protected function logMiddleware()
  180. {
  181. $formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG);
  182. return Middleware::log($this->app['logger'], $formatter, LogLevel::DEBUG);
  183. }
  184. /**
  185. * Return retry middleware.
  186. *
  187. * @return \Closure
  188. */
  189. protected function retryMiddleware()
  190. {
  191. return Middleware::retry(function (
  192. $retries,
  193. RequestInterface $request,
  194. ResponseInterface $response = null
  195. ) {
  196. // Limit the number of retries to 2
  197. if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) {
  198. // Retry on server errors
  199. $response = json_decode($body, true);
  200. if (!empty($response['errcode']) && in_array(abs($response['errcode']), [40001, 40014, 42001], true)) {
  201. $this->accessToken->refresh();
  202. $this->app['logger']->debug('Retrying with refreshed access token.');
  203. return true;
  204. }
  205. }
  206. return false;
  207. }, function () {
  208. return abs($this->app->config->get('http.retry_delay', 500));
  209. });
  210. }
  211. }