-
Notifications
You must be signed in to change notification settings - Fork 11.8k
Expand file tree
/
Copy pathRateLimitedWithRedis.php
More file actions
131 lines (114 loc) · 3.3 KB
/
RateLimitedWithRedis.php
File metadata and controls
131 lines (114 loc) · 3.3 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
121
122
123
124
125
126
127
128
129
130
131
<?php
namespace Illuminate\Queue\Middleware;
use Illuminate\Container\Container;
use Illuminate\Contracts\Redis\Factory as Redis;
use Illuminate\Redis\Limiters\DurationLimiter;
use Illuminate\Redis\Limiters\SlidingWindowDurationLimiter;
use Illuminate\Support\InteractsWithTime;
class RateLimitedWithRedis extends RateLimited
{
use InteractsWithTime;
/**
* The name of the Redis connection that should be used.
*
* @var string|null
*/
protected $connectionName = null;
/**
* The timestamp of the end of the current duration by key.
*
* @var array
*/
public $decaysAt = [];
/**
* Create a new middleware instance.
*
* @param string $limiterName
*/
public function __construct($limiterName, ?string $connection = null)
{
parent::__construct($limiterName);
$this->connectionName = $connection;
}
/**
* Handle a rate limited job.
*
* @param mixed $job
* @param callable $next
* @param array $limits
* @return mixed
*/
protected function handleJob($job, $next, array $limits)
{
foreach ($limits as $limit) {
if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decaySeconds, $limit->slidingWindow)) {
return $this->shouldRelease
? $job->release($this->releaseAfter ?: $this->getTimeUntilNextRetry($limit->key))
: false;
}
}
return $next($job);
}
/**
* Determine if the given key has been "accessed" too many times.
*
* @param string $key
* @param int $maxAttempts
* @param int $decaySeconds
* @param bool $slidingWindow
* @return bool
*/
protected function tooManyAttempts($key, $maxAttempts, $decaySeconds, $slidingWindow = false)
{
$redis = Container::getInstance()
->make(Redis::class)
->connection($this->connectionName);
$limiter = $slidingWindow
? new SlidingWindowDurationLimiter($redis, $key, $maxAttempts, $decaySeconds)
: new DurationLimiter($redis, $key, $maxAttempts, $decaySeconds);
return tap(! $limiter->acquire(), function () use ($key, $limiter) {
$this->decaysAt[$key] = $limiter->decaysAt;
});
}
/**
* Get the number of seconds that should elapse before the job is retried.
*
* @param string $key
* @param int $decaySeconds
* @param bool $slidingWindow
* @return int
*/
protected function getTimeUntilNextRetry($key, $decaySeconds = 60, $slidingWindow = false)
{
return ($this->decaysAt[$key] - $this->currentTime()) + 3;
}
/**
* Specify the Redis connection that should be used.
*
* @param string $name
* @return $this
*/
public function connection(string $name)
{
$this->connectionName = $name;
return $this;
}
/**
* Prepare the object for serialization.
*
* @return array
*/
public function __sleep()
{
return array_merge(parent::__sleep(), ['connectionName']);
}
/**
* Prepare the object after unserialization.
*
* @return void
*/
public function __wakeup()
{
parent::__wakeup();
}
}