调用方:
$response = (new \App\Library\httpclient\HttpClient())
->withHeader('key', 'value')
->withTimeout(3)
->withOnFailedRetry(3, 1)
->withResponseHeaders(['Journal-Id'])
->get("https://xxx.com/xxx");
// $response->getHTTPStatusCode();
// $response->getHeaders();
// $response->getBody();
// $response->getErrorMessage();
文件:App/Library/httpclient/HttpClient.php
<?php
namespace App\Library\httpclient;
use Closure;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class HttpClient
{
/**
* The http request header
*
* @var array
*/
protected $headers;
/**
* The http request timeout (second)
*
* The default timeout is 10s
*
* @var float
*/
protected $timeout = 10;
/**
* The retry times
*
* The default retry times is 0
*
* @var int
*/
protected $retryTimes = 0;
/**
*
* The retry delay (second)
*
* The default delay is 1s
*
* @var float
*/
protected $retryDelay = 1;
/**
* The retry http code
*
* The default retry http code
* 408 Request Timeout RFC 7231, 6.5.7
* 423 Locked RFC 4918, 11.3
* 425 Too Early RFC 8470, 5.2.
* 429 Too Many Requests RFC 6585, 4
* 503 Service Unavailable RFC 7231, 6.6.4
* 504 Gateway Timeout RFC 7231, 6.6.5
*
* @var array
*/
protected $retryHttpCode = [408, 423, 425, 429, 503, 504];
/**
* @var callable
*/
protected $retryDeciderCallable;
/**
* The response headers
*
* @var string[]
*/
protected $responseHeaders = ['Date'];
/**
* Configures a header item
*
* @param string $name
* @param mixed $value
*
* @return HttpClient
*/
public function withHeader(string $name, $value): HttpClient
{
$this->headers[$name] = $value;
return $this;
}
/**
* Configures timeout (second)
*
* @param float $timeout
*
* @return HttpClient
*/
public function withTimeout(float $timeout): HttpClient
{
$this->timeout = $timeout;
return $this;
}
/**
* Configures retry
*
* @param int $retryTimes
* @param float $retryDelay
* @param callable|null $retryDecider
* @return HttpClient
*/
public function withOnFailedRetry(int $retryTimes, float $retryDelay, callable $retryDecider = null): HttpClient
{
$this->retryTimes = $retryTimes;
$this->retryDelay = $retryDelay ?? $this->retryDelay;
if ($retryDecider) {
$this->retryDeciderCallable = $retryDecider;
} else {
$this->retryDeciderCallable = function (Response $response) {
if (in_array($response->getStatusCode(), $this->retryHttpCode)) {
return true;
}
return false;
};
}
return $this;
}
/**
* Configures response headers
*
* @param array $headers
*
* @return HttpClient
*/
public function withResponseHeaders(array $headers): HttpClient
{
$this->responseHeaders = array_merge($headers, $this->responseHeaders);
return $this;
}
/**
* get
*
* @param string $url
* @param array $query
*
* @return \App\Library\httpclient\Response
*/
public function get(string $url, array $query = []): \App\Library\httpclient\Response
{
$guzzleOptions = $this->guzzleHttpOptions();
$guzzleOptions['query'] = $query ?? [];
try {
$response = $this->response($this->guzzleHttpClient()->get($url, $guzzleOptions));
} catch (Throwable $throwable) {
$response = $this->response(null, $throwable->getMessage());
}
return $response;
}
/**
* postForm
*
* @param string $url
* @param array $form
*
* @return \App\Library\httpclient\Response
*/
public function postForm(string $url, array $form = []): \App\Library\httpclient\Response
{
$guzzleOptions = $this->guzzleHttpOptions();
$guzzleOptions['form_params'] = $form ?? [];
$guzzleOptions['headers']['Content-Type'] = 'application/x-www-form-urlencoded';
try {
$response = $this->response($this->guzzleHttpClient()->post($url, $guzzleOptions));
} catch (GuzzleException $e) {
$response = $this->response(null, $e->getMessage());
}
return $response;
}
/**
* postJson
*
* @param string $url
* @param array $json
*
* @return \App\Library\httpclient\Response
*/
public function postJson(string $url, array $json = []): \App\Library\httpclient\Response
{
$guzzleOptions = $this->guzzleHttpOptions();
$guzzleOptions['json'] = $json ?? [];
$guzzleOptions['headers']['Content-Type'] = 'application/json';
try {
$response = $this->response($this->guzzleHttpClient()->post($url, $guzzleOptions));
} catch (GuzzleException $e) {
$response = $this->response(null, $e->getMessage());
}
return $response;
}
/**
* guzzleOptions
*
* @return array
*/
private function guzzleHttpOptions(): array
{
$options['http_errors'] = false;
$options['headers'] = $this->headers ?? [];
$options['timeout'] = $this->timeout ?? 0;
return $options;
}
/**
* guzzleHttpClient
*
* @return Client
*/
private function guzzleHttpClient(): Client
{
$handlerStack = HandlerStack::create(new CurlHandler());
if (!empty($this->retryTimes)) {
$handlerStack->push(Middleware::retry($this->retryDecider(), $this->retryDelay()), 'retry');
}
return new Client(['handler' => $handlerStack]);
}
/**
* retryDecider
*
* @return Closure
*/
private function retryDecider(): Closure
{
return function (
$retries,
Request $request,
Response $response = null,
ConnectException $exception = null
) {
if ($retries >= $this->retryTimes) {
return false;
}
$shouldRetry = false;
if ($response) {
if (($this->retryDeciderCallable)($response)) {
$shouldRetry = true;
}
}
return $shouldRetry;
};
}
/**
* retryDelay
*
* @return Closure
*/
private function retryDelay(): Closure
{
return function () {
return 1000 * $this->retryDelay;
};
}
/**
* response
*
* @param ResponseInterface|null $guzzleResponse
* @param string $errorMessage
* @return \App\Library\httpclient\Response
*/
private function response(ResponseInterface $guzzleResponse = null, string $errorMessage = ''): \App\Library\httpclient\Response
{
$responseHeaders = [];
if (!empty($guzzleResponse)) {
foreach ($this->responseHeaders as $v) {
$responseHeaders[$v] = $guzzleResponse->getHeader($v);
}
}
return (new \App\Library\httpclient\Response(
(!empty($guzzleResponse) ? $guzzleResponse->getStatusCode() : 0),
($responseHeaders ?? []),
(!empty($guzzleResponse) ? $guzzleResponse->getBody()->getContents() : ''),
$errorMessage
));
}
}
文件:App/Library/httpclient/Response.php
<?php
namespace App\Library\httpclient;
class Response
{
/**
* @var int
*/
protected $statusCode;
/**
* @var array
*/
protected $headers;
/**
* @var string
*/
protected $body;
/**
* @var string
*/
protected $errorMessage;
public function __construct(int $statusCode, array $headers, string $body, string $errorMessage)
{
$this->statusCode = $statusCode;
$this->headers = $headers;
$this->body = $body;
$this->errorMessage = $errorMessage;
}
public function getHTTPStatusCode(): int
{
return $this->statusCode;
}
public function getHeaders(): array
{
return $this->headers;
}
public function getBody(): string
{
return $this->body;
}
public function getErrorMessage(): string
{
return $this->errorMessage;
}
}