forked from ArtSkills/common
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathClient.php
157 lines (134 loc) · 5.3 KB
/
Client.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<?php
declare(strict_types=1);
namespace ArtSkills\Http;
use ArtSkills\Lib\Arrays;
use Cake\Http\Client\Request;
use Cake\Http\Client\Response;
use Cake\Http\Exception\HttpException;
use ArtSkills\Lib\Env;
class Client extends \Cake\Http\Client
{
/** @var int Таймаут перед повторным подключением по-умолчанию (сек) */
public const DEFAULT_REPEAT_REQUEST_TIMEOUT = 10;
/** @var int Таймаут запроса по-умолчанию (сек) */
public const DEFAULT_TIMEOUT = 30;
/** @var string[] Заголовки по-умолчанию */
public const DEFAULT_HEADERS = [
'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36',
];
/** @var string Тип контента - json */
public const CONTENT_TYPE_JSON = 'application/json';
/** @var string[] Заголовки по-умолчанию только для POST запросов */
public const DEFAULT_POST_HEADERS = [
'Content-Type' => self::CONTENT_TYPE_JSON,
];
/** @var int Кол-в редиректов при запросе по-умолчанию */
public const DEFAULT_REDIRECT_COUNT = 2;
/** @var int[] Перечень ошибок CURL при, которых делается попытка повторного запроса */
private const REPEAT_REQUEST_CURL_ERROR = [6, 7, 18, 28, 35, 55, 56];
/** @var int Таймаут перед повторным подключением */
private int $_repeatRequestTimeout = self::DEFAULT_REPEAT_REQUEST_TIMEOUT;
/** @var bool Повторять ли запрос при неудаче */
private bool $_isRepeatRequest = true;
/**
* Client constructor.
*
* @param array $config
* @SuppressWarnings(PHPMD.MethodArgs)
* @phpstan-ignore-next-line
*/
public function __construct(array $config = [])
{
if (!array_key_exists('redirect', $config)) {
$config['redirect'] = self::DEFAULT_REDIRECT_COUNT;
}
if (!array_key_exists('timeout', $config)) {
$config['timeout'] = self::DEFAULT_TIMEOUT;
}
// Возможность глобального переопределения адаптера отправки запросов
if (Env::hasHttpClientAdapter()) {
$config['adapter'] = Env::getHttpClientAdapter();
}
parent::__construct($config);
}
/**
* @inheritDoc
* @phpstan-ignore-next-line
*/
protected function _doRequest($method, $url, $data, $options): Response
{
// Добавляем заголовков по-умолчанию
$options['headers'] = ($options['headers'] ?? []) + self::DEFAULT_HEADERS;
return parent::_doRequest($method, $url, $data, $options);
}
/**
* @inheritDoc
* @phpstan-ignore-next-line
*/
public function post($url, $data = [], array $options = [])
{
// Добавляем заголовков по-умолчанию для post запросов
$options['headers'] = ($options['headers'] ?? []) + self::DEFAULT_POST_HEADERS;
// Если 'Content-Type' => 'application/json', то $data должен быть json-строкой
if ($options['headers']['Content-Type'] === self::CONTENT_TYPE_JSON && is_array($data)) {
$data = Arrays::encode($data);
}
return parent::post($url, $data, $options);
}
/**
* Дважды отправляем запрос при таймауте
*
* @inheritDoc
* @phpstan-ignore-next-line
*/
protected function _sendRequest(Request $request, $options, callable $errorCallback = null): Response
{
try {
$result = parent::_sendRequest($request, $options);
} catch (HttpException $exception) {
if ($this->_isRepeatRequest && in_array($this->_getCurlErrorCode($exception->getMessage()), self::REPEAT_REQUEST_CURL_ERROR)) {
if (is_callable($errorCallback)) {
$errorCallback($exception);
}
sleep($this->_repeatRequestTimeout);
$result = parent::_sendRequest($request, $options);
} else {
throw $exception;
}
}
return $result;
}
/**
* Установить таймаут перед повторным запросе
*
* @param int $timeout
* @return $this
*/
public function setRepeatRequestTimeout(int $timeout): self
{
$this->_repeatRequestTimeout = $timeout;
return $this;
}
/**
* Отключить повторение запроса при неудаче
*
* @return $this
*/
public function doNotRepeatRequest(): self
{
$this->_isRepeatRequest = false;
return $this;
}
/**
* Извлекаем код ошибки CURL из сообщения об ошибке
*
* @param string $errorMessage
* @return int
*/
private function _getCurlErrorCode(string $errorMessage): int
{
$re = '/cURL Error \((\d+)\)/mi';
preg_match_all($re, $errorMessage, $matches, PREG_SET_ORDER);
return (int)($matches[0][1] ?? 0);
}
}