调用方:

    1. $response = (new \App\Library\httpclient\HttpClient())
    2. ->withHeader('key', 'value')
    3. ->withTimeout(3)
    4. ->withOnFailedRetry(3, 1)
    5. ->withResponseHeaders(['Journal-Id'])
    6. ->get("https://xxx.com/xxx");
    7. // $response->getHTTPStatusCode();
    8. // $response->getHeaders();
    9. // $response->getBody();
    10. // $response->getErrorMessage();

    文件:App/Library/httpclient/HttpClient.php

    1. <?php
    2. namespace App\Library\httpclient;
    3. use Closure;
    4. use GuzzleHttp\Client;
    5. use GuzzleHttp\Exception\ConnectException;
    6. use GuzzleHttp\Exception\GuzzleException;
    7. use GuzzleHttp\Handler\CurlHandler;
    8. use GuzzleHttp\HandlerStack;
    9. use GuzzleHttp\Middleware;
    10. use GuzzleHttp\Psr7\Request;
    11. use GuzzleHttp\Psr7\Response;
    12. use Psr\Http\Message\ResponseInterface;
    13. use Throwable;
    14. class HttpClient
    15. {
    16. /**
    17. * The http request header
    18. *
    19. * @var array
    20. */
    21. protected $headers;
    22. /**
    23. * The http request timeout (second)
    24. *
    25. * The default timeout is 10s
    26. *
    27. * @var float
    28. */
    29. protected $timeout = 10;
    30. /**
    31. * The retry times
    32. *
    33. * The default retry times is 0
    34. *
    35. * @var int
    36. */
    37. protected $retryTimes = 0;
    38. /**
    39. *
    40. * The retry delay (second)
    41. *
    42. * The default delay is 1s
    43. *
    44. * @var float
    45. */
    46. protected $retryDelay = 1;
    47. /**
    48. * The retry http code
    49. *
    50. * The default retry http code
    51. * 408 Request Timeout RFC 7231, 6.5.7
    52. * 423 Locked RFC 4918, 11.3
    53. * 425 Too Early RFC 8470, 5.2.
    54. * 429 Too Many Requests RFC 6585, 4
    55. * 503 Service Unavailable RFC 7231, 6.6.4
    56. * 504 Gateway Timeout RFC 7231, 6.6.5
    57. *
    58. * @var array
    59. */
    60. protected $retryHttpCode = [408, 423, 425, 429, 503, 504];
    61. /**
    62. * @var callable
    63. */
    64. protected $retryDeciderCallable;
    65. /**
    66. * The response headers
    67. *
    68. * @var string[]
    69. */
    70. protected $responseHeaders = ['Date'];
    71. /**
    72. * Configures a header item
    73. *
    74. * @param string $name
    75. * @param mixed $value
    76. *
    77. * @return HttpClient
    78. */
    79. public function withHeader(string $name, $value): HttpClient
    80. {
    81. $this->headers[$name] = $value;
    82. return $this;
    83. }
    84. /**
    85. * Configures timeout (second)
    86. *
    87. * @param float $timeout
    88. *
    89. * @return HttpClient
    90. */
    91. public function withTimeout(float $timeout): HttpClient
    92. {
    93. $this->timeout = $timeout;
    94. return $this;
    95. }
    96. /**
    97. * Configures retry
    98. *
    99. * @param int $retryTimes
    100. * @param float $retryDelay
    101. * @param callable|null $retryDecider
    102. * @return HttpClient
    103. */
    104. public function withOnFailedRetry(int $retryTimes, float $retryDelay, callable $retryDecider = null): HttpClient
    105. {
    106. $this->retryTimes = $retryTimes;
    107. $this->retryDelay = $retryDelay ?? $this->retryDelay;
    108. if ($retryDecider) {
    109. $this->retryDeciderCallable = $retryDecider;
    110. } else {
    111. $this->retryDeciderCallable = function (Response $response) {
    112. if (in_array($response->getStatusCode(), $this->retryHttpCode)) {
    113. return true;
    114. }
    115. return false;
    116. };
    117. }
    118. return $this;
    119. }
    120. /**
    121. * Configures response headers
    122. *
    123. * @param array $headers
    124. *
    125. * @return HttpClient
    126. */
    127. public function withResponseHeaders(array $headers): HttpClient
    128. {
    129. $this->responseHeaders = array_merge($headers, $this->responseHeaders);
    130. return $this;
    131. }
    132. /**
    133. * get
    134. *
    135. * @param string $url
    136. * @param array $query
    137. *
    138. * @return \App\Library\httpclient\Response
    139. */
    140. public function get(string $url, array $query = []): \App\Library\httpclient\Response
    141. {
    142. $guzzleOptions = $this->guzzleHttpOptions();
    143. $guzzleOptions['query'] = $query ?? [];
    144. try {
    145. $response = $this->response($this->guzzleHttpClient()->get($url, $guzzleOptions));
    146. } catch (Throwable $throwable) {
    147. $response = $this->response(null, $throwable->getMessage());
    148. }
    149. return $response;
    150. }
    151. /**
    152. * postForm
    153. *
    154. * @param string $url
    155. * @param array $form
    156. *
    157. * @return \App\Library\httpclient\Response
    158. */
    159. public function postForm(string $url, array $form = []): \App\Library\httpclient\Response
    160. {
    161. $guzzleOptions = $this->guzzleHttpOptions();
    162. $guzzleOptions['form_params'] = $form ?? [];
    163. $guzzleOptions['headers']['Content-Type'] = 'application/x-www-form-urlencoded';
    164. try {
    165. $response = $this->response($this->guzzleHttpClient()->post($url, $guzzleOptions));
    166. } catch (GuzzleException $e) {
    167. $response = $this->response(null, $e->getMessage());
    168. }
    169. return $response;
    170. }
    171. /**
    172. * postJson
    173. *
    174. * @param string $url
    175. * @param array $json
    176. *
    177. * @return \App\Library\httpclient\Response
    178. */
    179. public function postJson(string $url, array $json = []): \App\Library\httpclient\Response
    180. {
    181. $guzzleOptions = $this->guzzleHttpOptions();
    182. $guzzleOptions['json'] = $json ?? [];
    183. $guzzleOptions['headers']['Content-Type'] = 'application/json';
    184. try {
    185. $response = $this->response($this->guzzleHttpClient()->post($url, $guzzleOptions));
    186. } catch (GuzzleException $e) {
    187. $response = $this->response(null, $e->getMessage());
    188. }
    189. return $response;
    190. }
    191. /**
    192. * guzzleOptions
    193. *
    194. * @return array
    195. */
    196. private function guzzleHttpOptions(): array
    197. {
    198. $options['http_errors'] = false;
    199. $options['headers'] = $this->headers ?? [];
    200. $options['timeout'] = $this->timeout ?? 0;
    201. return $options;
    202. }
    203. /**
    204. * guzzleHttpClient
    205. *
    206. * @return Client
    207. */
    208. private function guzzleHttpClient(): Client
    209. {
    210. $handlerStack = HandlerStack::create(new CurlHandler());
    211. if (!empty($this->retryTimes)) {
    212. $handlerStack->push(Middleware::retry($this->retryDecider(), $this->retryDelay()), 'retry');
    213. }
    214. return new Client(['handler' => $handlerStack]);
    215. }
    216. /**
    217. * retryDecider
    218. *
    219. * @return Closure
    220. */
    221. private function retryDecider(): Closure
    222. {
    223. return function (
    224. $retries,
    225. Request $request,
    226. Response $response = null,
    227. ConnectException $exception = null
    228. ) {
    229. if ($retries >= $this->retryTimes) {
    230. return false;
    231. }
    232. $shouldRetry = false;
    233. if ($response) {
    234. if (($this->retryDeciderCallable)($response)) {
    235. $shouldRetry = true;
    236. }
    237. }
    238. return $shouldRetry;
    239. };
    240. }
    241. /**
    242. * retryDelay
    243. *
    244. * @return Closure
    245. */
    246. private function retryDelay(): Closure
    247. {
    248. return function () {
    249. return 1000 * $this->retryDelay;
    250. };
    251. }
    252. /**
    253. * response
    254. *
    255. * @param ResponseInterface|null $guzzleResponse
    256. * @param string $errorMessage
    257. * @return \App\Library\httpclient\Response
    258. */
    259. private function response(ResponseInterface $guzzleResponse = null, string $errorMessage = ''): \App\Library\httpclient\Response
    260. {
    261. $responseHeaders = [];
    262. if (!empty($guzzleResponse)) {
    263. foreach ($this->responseHeaders as $v) {
    264. $responseHeaders[$v] = $guzzleResponse->getHeader($v);
    265. }
    266. }
    267. return (new \App\Library\httpclient\Response(
    268. (!empty($guzzleResponse) ? $guzzleResponse->getStatusCode() : 0),
    269. ($responseHeaders ?? []),
    270. (!empty($guzzleResponse) ? $guzzleResponse->getBody()->getContents() : ''),
    271. $errorMessage
    272. ));
    273. }
    274. }

    文件:App/Library/httpclient/Response.php

    1. <?php
    2. namespace App\Library\httpclient;
    3. class Response
    4. {
    5. /**
    6. * @var int
    7. */
    8. protected $statusCode;
    9. /**
    10. * @var array
    11. */
    12. protected $headers;
    13. /**
    14. * @var string
    15. */
    16. protected $body;
    17. /**
    18. * @var string
    19. */
    20. protected $errorMessage;
    21. public function __construct(int $statusCode, array $headers, string $body, string $errorMessage)
    22. {
    23. $this->statusCode = $statusCode;
    24. $this->headers = $headers;
    25. $this->body = $body;
    26. $this->errorMessage = $errorMessage;
    27. }
    28. public function getHTTPStatusCode(): int
    29. {
    30. return $this->statusCode;
    31. }
    32. public function getHeaders(): array
    33. {
    34. return $this->headers;
    35. }
    36. public function getBody(): string
    37. {
    38. return $this->body;
    39. }
    40. public function getErrorMessage(): string
    41. {
    42. return $this->errorMessage;
    43. }
    44. }