-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathRateLimiting.php
More file actions
120 lines (100 loc) · 3.31 KB
/
RateLimiting.php
File metadata and controls
120 lines (100 loc) · 3.31 KB
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
<?php
declare(strict_types=1);
namespace Gnikyt\BasicShopifyAPI\Middleware;
use Gnikyt\BasicShopifyAPI\BasicShopifyAPI;
use Gnikyt\BasicShopifyAPI\Traits\IsRequestType;
use Psr\Http\Message\RequestInterface;
/**
* Handle basic request rate limiting for REST and GraphQL.
*/
class RateLimiting extends AbstractMiddleware
{
use IsRequestType;
/**
* Run.
*
* @param callable $handler
*
* @return callable
*/
public function __invoke(callable $handler): callable
{
$self = $this;
return function (RequestInterface $request, array $options) use ($self, $handler) {
if ($self->isRestRequest($request->getUri())) {
$this->handleRest($self->api);
} else {
$this->handleGraph($self->api);
}
return $handler($request, $options);
};
}
/**
* Handle REST checks.
*
* @param BasicShopifyAPI $api
*
* @return bool
*/
protected function handleRest(BasicShopifyAPI $api): bool
{
// Get the client
$client = $api->getRestClient();
$td = $client->getTimeDeferrer();
$ts = $client->getTimeStore();
$times = $ts->get($api->getSession());
if (count($times) < $api->getOptions()->getRestLimit()) {
// Not at our limit yet, allow through without limiting
return false;
}
// Determine if this call has passed the window
$windowTime = end($times) + 1000000;
$currentTime = $td->getCurrentTime();
if ($currentTime > $windowTime) {
// Call is passed the window, reset and allow through without limiting
$ts->reset($api->getSession());
return false;
}
// Call is inside the window and not at the call limit, sleep until window can be reset
$sleepTime = $windowTime - $currentTime;
$td->sleep($sleepTime < 0 ? 0 : $sleepTime);
$ts->reset($api->getSession());
return true;
}
/**
* Handle GraphQL checks.
*
* @param BasicShopifyAPI $api
*
* @return bool
*/
protected function handleGraph(BasicShopifyAPI $api): bool
{
// Get the client
$client = $api->getGraphClient();
$td = $client->getTimeDeferrer();
$ts = $client->getTimeStore();
$ls = $client->getLimitStore();
// Get current, last request time, and time difference
$currentTime = $td->getCurrentTime();
$lastTime = $ts->get($api->getSession());
$lastTime = $lastTime[0] ?? 0;
// Get the last request cost
$lastCost = $ls->get($api->getSession());
/** @var int $lastCost */
$lastCost = $lastCost[0]['actualCost'] ?? 0;
if ($lastTime === 0 || $lastCost === 0) {
// This is the first request, nothing to do
return false;
}
// How many points can be spent every second and time difference
$pointsEverySecond = $api->getOptions()->getGraphLimit();
$timeDiff = $currentTime - $lastTime;
if ($timeDiff < 1000000 && $lastCost > $pointsEverySecond) {
// Less than a second has passed and the cost is over the limit
$td->sleep(1000000 - $timeDiff);
return true;
}
return false;
}
}