Skip to content

Commit 0c7a9d6

Browse files
author
Tyler King
committed
Rate limiting adjusted to microseconds, option to optionally remove rate limiting
1 parent 902ed6f commit 0c7a9d6

File tree

14 files changed

+69
-57
lines changed

14 files changed

+69
-57
lines changed

README.md

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -408,17 +408,30 @@ $api = new BasicShopifyAPI($options);
408408
$api->rest('GET', '/admin/api/unstable/shop.json'); // Will ignore "2020-01" version and use "unstable" for this request
409409
```
410410

411-
### Rate Limiting
411+
### Rate limiting
412412

413-
This library comes with a built-in basic rate limiter via `usleep`.
413+
This library comes with a built-in basic rate limiter which utilizes `usleep` between applicable calls.
414414

415-
For REST, it ensures you do not request more than the default of 2 calls per second.
416-
417-
For GraphQL, it ensures you do not use more than the default of 50 points per second.
415+
* For REST: it ensures you do not request more than the default of 2 calls per second.
416+
* For GraphQL: it ensures you do not use more than the default of 50 points per second.
418417

419418
To adjust the default limits, use the option class' `setRestLimit` and `setGraphLimit`.
420419

421-
### page_info / pagination Support
420+
#### Custom rate limiting
421+
422+
You simply need to disable the built-in rate limiter and push in a custom Guzzle middleware. Example:
423+
424+
```php
425+
$options = new Options();
426+
// ...
427+
$options->disableRateLimiting();
428+
429+
// ...
430+
$api = new BasicShopifyAPI($options);
431+
$api->addMiddleware(new CustomRateLimiter($api), 'rate:limiting');
432+
```
433+
434+
### page_info / pagination support
422435

423436
2019-07 API version introduced a new `Link` header which is used for pagination ([explained here](https://help.shopify.com/en/api/guides/paginated-rest-results)).
424437

src/Osiset/BasicShopifyAPI/BasicShopifyAPI.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,12 @@ public function __construct(
122122
$this->stack = HandlerStack::create($this->getOptions()->getGuzzleHandler());
123123
$this
124124
->addMiddleware(new AuthRequest($this), 'request:auth')
125-
->addMiddleware(new RateLimiting($this), 'rate:limiting')
126125
->addMiddleware(new UpdateApiLimits($this), 'rate:update')
127126
->addMiddleware(new UpdateRequestTime($this), 'time:update')
128127
->addMiddleware(GuzzleRetryMiddleware::factory(), 'request:retry');
128+
if ($this->getOptions()->isRateLimitingEnabled()) {
129+
$this->addMiddleware(new RateLimiting($this), 'rate:limiting');
130+
}
129131

130132
// Create a default Guzzle client with our stack
131133
$this->setClient(
@@ -182,7 +184,6 @@ public function getOptions(): Options
182184
public function setGraphClient(GraphRequester $client): self
183185
{
184186
$this->graphClient = $client;
185-
186187
return $this;
187188
}
188189

@@ -206,7 +207,6 @@ public function getGraphClient(): GraphRequester
206207
public function setRestClient(RestRequester $client): self
207208
{
208209
$this->restClient = $client;
209-
210210
return $this;
211211
}
212212

@@ -252,7 +252,6 @@ public function withSession(Session $session, Closure $closure)
252252
// Clone the API class and bind it to the closure
253253
$clonedApi = clone $this;
254254
$clonedApi->setSession($session);
255-
256255
return $closure->call($clonedApi);
257256
}
258257

@@ -267,7 +266,6 @@ public function withSession(Session $session, Closure $closure)
267266
public function addMiddleware(callable $callable, string $name = ''): self
268267
{
269268
$this->stack->push($callable, $name);
270-
271269
return $this;
272270
}
273271

@@ -281,7 +279,6 @@ public function addMiddleware(callable $callable, string $name = ''): self
281279
public function removeMiddleware(string $name = ''): self
282280
{
283281
$this->stack->remove($name);
284-
285282
return $this;
286283
}
287284

@@ -356,7 +353,6 @@ public function verifyRequest(array $params): bool
356353
// Grab the HMAC, remove it from the params, then sort the params for hashing
357354
$hmac = $params['hmac'];
358355
unset($params['hmac']);
359-
360356
ksort($params);
361357

362358
// Encode and hash the params (without HMAC), add the API secret, and compare to the HMAC from params

src/Osiset/BasicShopifyAPI/Clients/Graph.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,12 @@ public function request(string $query, array $variables = [], bool $sync = true)
4545
if ($sync === false) {
4646
// Async request
4747
$promise = $requestFn($request);
48-
4948
return $promise->then([$this, 'handleSuccess'], [$this, 'handleFailure']);
5049
}
5150

5251
// Sync request (default)
5352
try {
5453
$response = $requestFn($request);
55-
5654
return $this->handleSuccess($response);
5755
} catch (RequestException $e) {
5856
return $this->handleFailure($e);

src/Osiset/BasicShopifyAPI/Clients/Rest.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ protected function extractLinkHeader(string $header): ResponseAccess
3131
preg_match(str_replace('{type}', $type, $regex), $header, $matches);
3232
$links[$type] = isset($matches[1]) ? $matches[1] : null;
3333
}
34-
3534
return new ResponseAccess($links);
3635
}
3736

@@ -63,10 +62,8 @@ public function requestAccess(string $code): ResponseAccess
6362
);
6463
} catch (ClientException $e) {
6564
$body = json_decode($e->getResponse()->getBody()->getContents());
66-
6765
throw new Exception($body->error_description);
6866
}
69-
7067
return $this->toResponse($response->getBody());
7168
}
7269

@@ -131,14 +128,12 @@ public function request(string $type, string $path, array $params = null, array
131128
*/
132129
$requestFn = function () use ($sync, $type, $uri, $guzzleParams) {
133130
$fn = $sync ? 'request' : 'requestAsync';
134-
135131
return $this->getClient()->{$fn}($type, $uri, $guzzleParams);
136132
};
137133

138134
if ($sync === false) {
139135
// Async request
140136
$promise = $requestFn();
141-
142137
return $promise->then([$this, 'handleSuccess'], [$this, 'handleFailure']);
143138
}
144139

src/Osiset/BasicShopifyAPI/Deferrers/Sleep.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Sleep implements TimeDeferrer
1515
*/
1616
public function getCurrentTime(): float
1717
{
18-
return microtime(true);
18+
return microtime(true) * 1000000;
1919
}
2020

2121
/**

src/Osiset/BasicShopifyAPI/Middleware/AuthRequest.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ class AuthRequest extends AbstractMiddleware
2929
public function __invoke(callable $handler): callable
3030
{
3131
$self = $this;
32-
3332
return function (RequestInterface $request, array $options) use ($self, $handler) {
3433
// Get the request URI
3534
$uri = $request->getUri();
@@ -81,7 +80,6 @@ public function __invoke(callable $handler): callable
8180
$this->versionPath($uri->getPath())
8281
)
8382
);
84-
8583
return $handler($request, $options);
8684
};
8785
}

src/Osiset/BasicShopifyAPI/Middleware/RateLimiting.php

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@ class RateLimiting extends AbstractMiddleware
2323
public function __invoke(callable $handler): callable
2424
{
2525
$self = $this;
26-
2726
return function (RequestInterface $request, array $options) use ($self, $handler) {
2827
if ($self->isRestRequest($request->getUri())) {
2928
$this->handleRest($self->api);
3029
} else {
3130
$this->handleGraph($self->api);
3231
}
33-
3432
return $handler($request, $options);
3533
};
3634
}
@@ -57,21 +55,19 @@ protected function handleRest(BasicShopifyAPI $api): bool
5755

5856
// Determine if this call has passed the window
5957
$firstTime = end($times);
60-
$windowTime = $firstTime + 1;
58+
$windowTime = $firstTime + 1000000;
6159
$currentTime = $td->getCurrentTime();
6260

6361
if ($currentTime > $windowTime) {
6462
// Call is passed the window, reset and allow through without limiting
6563
$ts->reset($api->getSession());
66-
6764
return false;
6865
}
6966

7067
// Call is inside the window and not at the call limit, sleep until window can be reset
7168
$sleepTime = $windowTime - $currentTime;
7269
$td->sleep($sleepTime < 0 ? 0 : $sleepTime);
7370
$ts->reset($api->getSession());
74-
7571
return true;
7672
}
7773

@@ -99,7 +95,6 @@ protected function handleGraph(BasicShopifyAPI $api): bool
9995
/** @var int $lastCost */
10096
$lastCost = $ls->get($api->getSession());
10197
$lastCost = isset($lastCost[0]) && isset($lastCost[0]['actualCost']) ? $lastCost[0]['actualCost'] : 0;
102-
10398
if ($lastTime === 0 || $lastCost === 0) {
10499
// This is the first request, nothing to do
105100
return false;
@@ -108,14 +103,11 @@ protected function handleGraph(BasicShopifyAPI $api): bool
108103
// How many points can be spent every second and time difference
109104
$pointsEverySecond = $api->getOptions()->getGraphLimit();
110105
$timeDiff = $currentTime - $lastTime;
111-
112106
if ($timeDiff < 1000000 && $lastCost > $pointsEverySecond) {
113107
// Less than a second has passed and the cost is over the limit
114108
$td->sleep(1000000 - $timeDiff);
115-
116109
return true;
117110
}
118-
119111
return false;
120112
}
121113
}

src/Osiset/BasicShopifyAPI/Middleware/UpdateApiLimits.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ class UpdateApiLimits extends AbstractMiddleware
2626
public function __invoke(callable $handler): callable
2727
{
2828
$self = $this;
29-
3029
return function (RequestInterface $request, array $options) use ($self, $handler) {
3130
$promise = $handler($request, $options);
3231

@@ -37,7 +36,6 @@ function (ResponseInterface $response) use ($self) {
3736
} else {
3837
$self->updateGraphCosts($response);
3938
}
40-
4139
return $response;
4240
}
4341
);

src/Osiset/BasicShopifyAPI/Middleware/UpdateRequestTime.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ class UpdateRequestTime extends AbstractMiddleware
2222
public function __invoke(callable $handler): callable
2323
{
2424
$self = $this;
25-
2625
return function (RequestInterface $request, array $options) use ($self, $handler) {
2726
// Get the client
2827
$api = $self->api;
@@ -34,7 +33,6 @@ public function __invoke(callable $handler): callable
3433
$client->getTimeDeferrer()->getCurrentTime(),
3534
$api->getSession()
3635
);
37-
3836
return $handler($request, $options);
3937
};
4038
}

src/Osiset/BasicShopifyAPI/Options.php

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ class Options
6565
*/
6666
protected $version;
6767

68+
/**
69+
* Enable or disable built-in rate limiting.
70+
*
71+
* @var bool
72+
*/
73+
protected $rateLimiting = true;
74+
6875
/**
6976
* Additional Guzzle options.
7077
*
@@ -98,7 +105,6 @@ class Options
98105
public function setType(bool $private): self
99106
{
100107
$this->private = $private;
101-
102108
return $this;
103109
}
104110

@@ -142,7 +148,6 @@ public function isPublic(): bool
142148
public function setApiKey(string $apiKey): self
143149
{
144150
$this->apiKey = $apiKey;
145-
146151
return $this;
147152
}
148153

@@ -166,7 +171,6 @@ public function getApiKey(): ?string
166171
public function setApiSecret(string $apiSecret): self
167172
{
168173
$this->apiSecret = $apiSecret;
169-
170174
return $this;
171175
}
172176

@@ -190,7 +194,6 @@ public function getApiSecret(): ?string
190194
public function setApiPassword(string $apiPassword): self
191195
{
192196
$this->apiPassword = $apiPassword;
193-
194197
return $this;
195198
}
196199

@@ -214,7 +217,6 @@ public function getApiPassword(): ?string
214217
public function setRestLimit(int $limit): self
215218
{
216219
$this->restLimit = $limit;
217-
218220
return $this;
219221
}
220222

@@ -238,7 +240,6 @@ public function getRestLimit(): int
238240
public function setGraphLimit(int $limit): self
239241
{
240242
$this->graphLimit = $limit;
241-
242243
return $this;
243244
}
244245

@@ -262,7 +263,6 @@ public function getGraphLimit(): int
262263
public function setGuzzleOptions(array $options): self
263264
{
264265
$this->guzzleOptions = array_merge($this->guzzleOptions, $options);
265-
266266
return $this;
267267
}
268268

@@ -286,7 +286,6 @@ public function getGuzzleOptions(): array
286286
public function setGuzzleHandler(callable $handler): self
287287
{
288288
$this->guzzleHandler = $handler;
289-
290289
return $this;
291290
}
292291

@@ -317,7 +316,6 @@ public function setVersion(string $version): self
317316
}
318317

319318
$this->version = $version;
320-
321319
return $this;
322320
}
323321

@@ -330,4 +328,36 @@ public function getVersion(): ?string
330328
{
331329
return $this->version;
332330
}
331+
332+
/**
333+
* Enable built-in rate limiting.
334+
*
335+
* @return self
336+
*/
337+
public function enableRateLimiting(): self
338+
{
339+
$this->rateLimiting = true;
340+
return $this;
341+
}
342+
343+
/**
344+
* Disable built-in rate limiting.
345+
*
346+
* @return self
347+
*/
348+
public function disableRateLimiting(): self
349+
{
350+
$this->rateLimiting = false;
351+
return $this;
352+
}
353+
354+
/**
355+
* Is built-in rate limiting enabled?
356+
*
357+
* @return bool
358+
*/
359+
public function isRateLimitingEnabled(): bool
360+
{
361+
return $this->rateLimiting;
362+
}
333363
}

0 commit comments

Comments
 (0)