-
-
Notifications
You must be signed in to change notification settings - Fork 118
Description
Description
When using the HasRateLimits trait on a Request class (rather than a Connector), and configuring getTooManyAttemptsLimiter() with ->sleep(), a fatal error occurs when a 429 response is received.
The error happens at line 102 of HasRateLimits.php where $this->send() is called, but send() only exists on the Connector class, not on Request.
Why I Need Rate Limiting on Request
I need to define rate limits at the Request level because:
- Different endpoints have different rate limits - Each API endpoint has its own rate/burst values
handleTooManyAttempts()uses request-specific data - When a 429 occurs, I calculate the wait time based on properties defined on the request (e.g.,$rate,$burst)
// Each request has its own rate limit configuration
class GetOrderItems extends AbstractRequest
{
protected float $rate = 0.5; // 0.5 requests/second
protected int $burst = 30; // Burst of 30
protected function handleTooManyAttempts(Response $response, Limit $limit): void
{
if ($response->status() !== 429) {
return;
}
// Calculate wait time based on THIS request's rate/burst
$secondsNeeded = (int) ceil($this->burst / $this->rate);
$limit->exceeded(releaseInSeconds: $secondsNeeded);
}
}Moving the trait to the Connector would lose this per-request context.
Steps to Reproduce
- Add HasRateLimits trait to a Request class
- Override getTooManyAttemptsLimiter() with ->sleep() enabled
- Make a request that returns a 429 response
Minimal Example
<?php
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\RateLimitPlugin\Limit;
use Saloon\RateLimitPlugin\Traits\HasRateLimits;
use Saloon\RateLimitPlugin\Stores\MemoryStore;
class MyRequest extends Request
{
use HasRateLimits;
protected ?string $method = 'GET';
// Request-specific rate limit values
protected float $rate = 0.5;
protected int $burst = 30;
public function resolveEndpoint(): string
{
return '/api/resource';
}
protected function resolveLimits(): array
{
return [
Limit::allow(10)->everyMinute()->sleep(),
];
}
protected function resolveRateLimitStore(): MemoryStore
{
return new MemoryStore();
}
protected function getTooManyAttemptsLimiter(): ?Limit
{
return Limit::custom($this->handleTooManyAttempts(...))->sleep();
}
protected function handleTooManyAttempts(Response $response, Limit $limit): void
{
if ($response->status() !== 429) {
return;
}
// Uses request-specific properties
$secondsNeeded = (int) ceil($this->burst / $this->rate);
$limit->exceeded(releaseInSeconds: $secondsNeeded);
}
}Expected Behavior
When a 429 response is received and ->sleep() is enabled, Saloon should wait and retry the request automatically.
Actual Behavior
Warning
Error: Call to undefined method MyRequest::send() at vendor/saloonphp/rate-limit-plugin/src/Traits/HasRateLimits.php:102
Root Cause
In HasRateLimits.php lines 94-102:
if (isset($limitThatWasExceeded)) {
if (! $limit->getShouldSleep()) {
$this->throwLimitException($limitThatWasExceeded);
}
// This line fails when trait is on Request
return $this->send($response->getRequest());
}When wasManuallyExceeded() is true and sleep() is enabled, the code calls $this->send() to retry. This works when the trait is on a Connector (which has send()), but fails when the trait is on a Request.
Environment
- PHP: 8.3
- Saloon: 3.x