Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
loadlimiter.php
1<?php
2
4
11use Bitrix\Bitrix24\Feature;
12
18{
19 private const MODULE_ID = 'rest';
20 private const BITRIX24_CONNECTOR_NAME = 'cache.redis';
21 private const CACHE_EXPIRE_TIME_PREFIX = 'expire';
22 private const DEFAULT_DOMAIN = 'default';
23 private static int $version = 2;
24 private static int $bucketSize = 60; //sec
25 private static int $bucketCount = 10;
26 private static int $limitTime = 420; // total hits duration per 10 min
27 private static float $minimalFixTime = 0.1;
28 private static string $domain = '';
29 private static ?bool $isActive = null;
30 private static bool $isFinaliseInit = false;
31 private static array $ignoreMethod = [
33 ];
34 private static array $limitedEntityTypes = [
35 APAuth\Auth::AUTH_TYPE,
36 OAuth\Auth::AUTH_TYPE,
37 ];
38 private static int $numBucket = 0;
39 private static array $timeRegistered = [];
40
46 public static function getLimitTime(): int
47 {
48 if (Loader::includeModule('bitrix24'))
49 {
50 $result = static::$limitTime;
51 $seconds = (int)Feature::getVariable('rest_load_limiter_seconds');
52 if ($seconds > 0)
53 {
54 $result = $seconds;
55 }
56 }
57 else
58 {
59 $result = (int)Option::get(static::MODULE_ID, 'load_limiter_second_limit', static::$limitTime);
60 }
61
62 return $result;
63 }
64
71 public static function isActive(): bool
72 {
73 if (is_null(static::$isActive))
74 {
75 if (Loader::includeModule('bitrix24'))
76 {
77 static::$isActive = true;
78 }
79 else
80 {
81 static::$isActive = Option::get(static::MODULE_ID, 'load_limiter_active', 'N') === 'Y';
82 }
83 }
84
85 return static::$isActive;
86 }
87
88 private static function getDomain(): string
89 {
90 if (static::$domain === '')
91 {
92 if (Loader::includeModule('bitrix24') && defined('BX24_HOST_NAME'))
93 {
94 static::$domain = BX24_HOST_NAME;
95 }
96 else
97 {
98 static::$domain = static::DEFAULT_DOMAIN;
99 }
100 }
101
102 return static::$domain;
103 }
104
111 public static function registerStarting($entityType, $entity, $method): void
112 {
113 if (
114 static::isActive()
115 || in_array($entityType, static::$limitedEntityTypes, true)
116 || !in_array($method, static::$ignoreMethod, true)
117 )
118 {
119 $key = static::getKey($entityType, $entity, $method);
120 if (!(static::$timeRegistered[$key] ?? null))
121 {
122 static::$timeRegistered[$key] = [
123 'entityType' => $entityType,
124 'entity' => $entity,
125 'method' => $method,
126 'timeStart' => [],
127 'timeFinish' => [],
128 ];
129 }
130
131 static::$timeRegistered[$key]['timeStart'][] = microtime(true);
132 }
133 }
134
142 public static function registerEnding($entityType, $entity, $method): void
143 {
144 if (
145 static::isActive()
146 && in_array($entityType, static::$limitedEntityTypes, true)
147 && !in_array($method, static::$ignoreMethod, true)
148 )
149 {
150 $key = static::getKey($entityType, $entity, $method);
151 if (static::$timeRegistered[$key])
152 {
153 static::$timeRegistered[$key]['timeFinish'][] = microtime(true);
154 }
155
156 if (!static::$isFinaliseInit)
157 {
158 static::$isFinaliseInit = true;
159 Application::getInstance()->addBackgroundJob([__CLASS__, 'finalize']);
160 }
161 }
162 }
163
172 public static function is($entityType, $entity, $method): bool
173 {
174 if (
175 !static::isActive()
176 || !in_array($entityType, static::$limitedEntityTypes, true)
177 || in_array($method, static::$ignoreMethod, true)
178 )
179 {
180 return false;
181 }
182
183 $totalTime = static::getRestTime($entityType, $entity, $method);
184 if ($totalTime > static::getLimitTime())
185 {
186 if (Loader::includeModule('bitrix24') && function_exists('saveRestStat'))
187 {
188 saveRestStat(static::getDomain(), $entityType, $entity, $method, $totalTime);
189 }
190
191 return true;
192 }
193
194 return false;
195 }
196
197 private static function getKey($entityType, $entity, $method, $bucketNum = null): string
198 {
199 return
200 static::getDomain() . '|v' . static::$version . '|'
201 . sha1($entityType . '|' .$entity . '|' . $method)
202 . '|' . $bucketNum;
203 }
204
215 public static function getResetTime($entityType, $entity, $method): ?int
216 {
217 $result = null;
218
219 if (static::isActive())
220 {
221 $resource = static::getConnectionResource();
222 if ($resource)
223 {
224 $numBucket = static::getNumBucket();
225 $key = static::getKey($entityType, $entity, $method);
226 $keyExpire = static::CACHE_EXPIRE_TIME_PREFIX . '|' . $key;
227 for ($i = static::$bucketCount - 1; $i >= 0; $i--)
228 {
229 $time = (float)$resource->get($key . ($numBucket - $i));
230 if ($time > 0 && $resource->exists($keyExpire . ($numBucket - $i)))
231 {
232 $result = (int)$resource->get($keyExpire . ($numBucket - $i));
233 break;
234 }
235 }
236 if (!$result)
237 {
238 if (!empty(static::$timeRegistered))
239 {
240 $item = reset(static::$timeRegistered);
241 if (!empty($item['timeStart']))
242 {
243 $firstTimeStart = reset($item['timeStart']);
244 $result = $firstTimeStart + static::$bucketCount * static::$bucketSize;
245 }
246 }
247 }
248 }
249 }
250
251 return $result;
252 }
253
254 protected static function getNumBucket()
255 {
256 if (!static::$numBucket)
257 {
258 static::$numBucket = intdiv(time(), static::$bucketSize);
259 }
260
261 return static::$numBucket;
262 }
263
272 public static function getRestTime($entityType, $entity, $method): float
273 {
274 $result = [];
275 if (static::isActive())
276 {
277 $numBucket = static::getNumBucket();
278
279 $key = static::getKey($entityType, $entity, $method);
280 $resource = static::getConnectionResource();
281 if ($resource)
282 {
283 for ($i = 0; $i < static::$bucketCount; $i++)
284 {
285 $result[] = (float)$resource->get($key . ($numBucket - $i));
286 }
287 }
288 if (!empty(static::$timeRegistered[$key]['timeStart']))
289 {
290 foreach (static::$timeRegistered[$key]['timeStart'] as $k => $timeStart)
291 {
292 if (static::$timeRegistered[$key]['timeFinish'][$k] ?? null)
293 {
294 $time = static::$timeRegistered[$key]['timeFinish'][$k] - $timeStart;
295 if ($time > static::$minimalFixTime)
296 {
297 $result[] = $time;
298 }
299 }
300 }
301 }
302 }
303
304 return array_sum($result);
305 }
306
312 public static function finalize(): void
313 {
314 if (static::$timeRegistered && static::isActive())
315 {
316 $resource = static::getConnectionResource();
317 if ($resource)
318 {
319 foreach (static::$timeRegistered as $item)
320 {
321 $time = 0;
322 $firstTime = reset($item['timeStart']);
323 foreach ($item['timeStart'] as $k => $timeStart)
324 {
325 if ($item['timeFinish'][$k])
326 {
327 $time += $item['timeFinish'][$k] - $timeStart;
328 }
329 }
330
331 if ($time > static::$minimalFixTime)
332 {
333 $key = static::getKey($item['entityType'], $item['entity'], $item['method'], static::getNumBucket());
334 if ($resource->exists($key))
335 {
336 $resource->incrByFloat($key, $time);
337 }
338 else
339 {
340 $expireAt = $firstTime + static::$bucketCount * static::$bucketSize;
341 $resource->incrByFloat($key, $time);
342 $resource->expire($key, $expireAt);
343
344 $keyExpire = static::CACHE_EXPIRE_TIME_PREFIX . '|' . $key;
345 $resource->incrByFloat($keyExpire, $expireAt);
346 $resource->expire($keyExpire, $expireAt);
347 }
348 }
349 }
350 static::$timeRegistered = [];
351 }
352 }
353 }
354
355 private static function getConnectionResource(): ?object
356 {
357 $result = null;
358 $connectionName = static::getConnectionName();
359 if ($connectionName)
360 {
361 $connection = Application::getInstance()
362 ->getConnectionPool()
363 ->getConnection($connectionName);
364
365 if ($connection && $connection->isConnected() === true)
366 {
367 $result = $connection->getResource();
368 }
369 }
370
371 return $result;
372 }
373
374 private static function getConnectionName(): string
375 {
376 if (Loader::includeModule('bitrix24'))
377 {
378 return static::BITRIX24_CONNECTOR_NAME;
379 }
380
381 return '';
382 }
383}
static is($entityType, $entity, $method)
static registerStarting($entityType, $entity, $method)
static registerEnding($entityType, $entity, $method)
static getResetTime($entityType, $entity, $method)
static getRestTime($entityType, $entity, $method)