Skip to content

Commit 54a71dd

Browse files
committed
minor #14370 [RateLimiter] Added the docs for the new component (javiereguiluz)
This PR was squashed before being merged into the 5.x branch. Discussion ---------- [RateLimiter] Added the docs for the new component I'd like to merge this soon, so we can later iterate the docs with the changes done in RateLimiter during the stabilization phase before Symfony 5.2 release. Thanks! Some questions for @wouterj: * Is the `strategy` config option mandatory? If yes, could we assign a default value to it to lower the learning curve? * I'm a bit lost with the "burst" behavior and the "limit" options. I don't fully understand them. * What's the purpose of the top level `interval` option? Is the same as the `rate.interval` option? Commits ------- d90991e [RateLimiter] Added the docs for the new component
2 parents 7809ea9 + d90991e commit 54a71dd

File tree

2 files changed

+196
-0
lines changed

2 files changed

+196
-0
lines changed

index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Topics
5252
notifier
5353
performance
5454
profiler
55+
rate_limiter
5556
routing
5657
security
5758
session

rate_limiter.rst

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
Rate Limiter
2+
============
3+
4+
.. versionadded:: 5.2
5+
6+
The RateLimiter component was introduced in Symfony 5.2 as an
7+
:doc:`experimental feature </contributing/code/experimental>`.
8+
9+
A "rate limiter" controls how frequently some event (e.g. an HTTP request or a
10+
login attempt) is allowed to happen. Rate limiting is commonly used as a
11+
defensive measure to protect services from excessive use (intended or not) and
12+
maintain their availability. It's also useful to control your internal or
13+
outbound processes (e.g. limit the number of simultaneously processed messages).
14+
15+
Symfony uses these rate limiters in built-in features like "login throttling",
16+
which limits how many failed login attempts a user can make in a given period of
17+
time, but you can use them for your own features too.
18+
19+
Rate Limiting Strategies
20+
------------------------
21+
22+
Symfony's rate limiter implements two of the most common strategies to enforce
23+
rate limits: **fixed window** and **token bucket**.
24+
25+
Fixed Window Rate Limiter
26+
~~~~~~~~~~~~~~~~~~~~~~~~~
27+
28+
This is the simplest technique and it's based on setting a limit for a given
29+
interval of time. For example: 5,000 requests per hour or 3 login attempts
30+
every 15 minutes.
31+
32+
Its main drawback is that resource usage is not evenly distributed in time and
33+
it can overload the server at the window edges. In the previous example, a user
34+
could make the 4,999 requests in the last minute of some hour and another 5,000
35+
requests during the first minute of the next hour, making 9,999 requests in
36+
total in two minutes and possibly overloading the server.
37+
38+
Token Bucket Rate Limiter
39+
~~~~~~~~~~~~~~~~~~~~~~~~~
40+
41+
This technique implements the `token bucket algorithm`_, which defines a
42+
continuously updating budget of resource usage. It roughly works like this:
43+
44+
* A bucket is created with an initial set of tokens;
45+
* A new token is added to the bucket with a predefined frequency (e.g. every second);
46+
* Allowing an event consumes one or more tokens;
47+
* If the bucket still contains tokens, the event is allowed; otherwise, it's denied;
48+
* If the bucket is at full capacity, new tokens are discarded.
49+
50+
Installation
51+
------------
52+
53+
Before using a rate limiter for the first time, run the following command to
54+
install the associated Symfony Component in your application:
55+
56+
.. code-block:: terminal
57+
58+
$ composer require symfony/rate-limiter
59+
60+
Configuration
61+
-------------
62+
63+
The following example creates two different rate limiters for an API service, to
64+
enforce different levels of service (free or paid):
65+
66+
.. code-block:: yaml
67+
68+
# config/packages/rate_limiter.yaml
69+
framework:
70+
rate_limiter:
71+
anonymous_api:
72+
strategy: fixed_window
73+
limit: 100
74+
interval: '60 minutes'
75+
authenticated_api:
76+
strategy: token_bucket
77+
limit: 5000
78+
rate: { interval: '15 minutes', amount: 500 }
79+
80+
.. note::
81+
82+
The value of the ``interval`` option must be a number followed by any of the
83+
units accepted by the `PHP date relative formats`_ (e.g. ``3 seconds``,
84+
``10 hours``, ``1 day``, etc.)
85+
86+
In the ``anonymous_api`` limiter, after making the first HTTP request, you can
87+
make up to 100 requests in the next 60 minutes. After that time, the counter
88+
resets and you have another 100 requests for the following 60 minutes.
89+
90+
In the ``authenticated_api`` limiter, after making the first HTTP request you
91+
are allowed to make up to 5,000 HTTP requests in total, and this number grows
92+
at a rate of another 500 requests every 15 minutes. If you don't make that
93+
number of requests, the unused ones don't accumulate (the ``limit`` option
94+
prevents that number from being higher than 5,000).
95+
96+
Rate Limiting in Action
97+
-----------------------
98+
99+
After having installed and configured the rate limiter, inject it in any service
100+
or controller and call the ``consume()`` method to try to consume a given number
101+
of tokens. For example, this controller uses the previous rate limiter to control
102+
the number of requests to the API::
103+
104+
// src/Controller/ApiController.php
105+
namespace App\Controller;
106+
107+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
108+
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
109+
use Symfony\Component\RateLimiter\Limiter;
110+
111+
class ApiController extends AbstractController
112+
{
113+
// the variable name must be: "rate limiter name" + "limiter" suffix
114+
public function index(Limiter $anonymousApiLimiter)
115+
{
116+
// create a limiter based on a unique identifier of the client
117+
// (e.g. the client's IP address, a username/email, an API key, etc.)
118+
$limiter = $anonymousApiLimiter->create($request->getClientIp());
119+
120+
// the argument of consume() is the number of tokens to consume
121+
// and returns an object of type Limit
122+
if (false === $anonymous_api_limiter->consume(1)->isAccepted()) {
123+
throw new TooManyRequestsHttpException();
124+
}
125+
126+
// you can also use the ensureAccepted() method - which throws a
127+
// RateLimitExceededException if the limit has been reached
128+
// $limiter->consume(1)->ensureAccepted();
129+
130+
// ...
131+
}
132+
133+
// ...
134+
}
135+
136+
.. note::
137+
138+
In a real application, instead of checking the rate limiter in all the API
139+
controller methods, create an :doc:`event listener or subscriber </event_dispatcher>`
140+
for the :ref:`kernel.request event <component-http-kernel-kernel-request>`
141+
and check the rate limiter once for all requests.
142+
143+
In other scenarios you may want instead to wait as long as needed until a new
144+
token is available. In those cases, use the ``wait()`` method::
145+
146+
// src/Controller/ApiController.php
147+
namespace App\Controller;
148+
149+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
150+
use Symfony\Component\HttpFoundation\Request;
151+
use Symfony\Component\RateLimiter\Limiter;
152+
153+
class ApiController extends AbstractController
154+
{
155+
public function registerUser(Request $request, Limiter $authenticatedApiLimiter)
156+
{
157+
$apiKey = $request->headers->get('apikey');
158+
$limiter = $authenticatedApiLimiter->create($apiKey);
159+
160+
// this blocks the application until the given number of tokens can be consumed
161+
do {
162+
$limit = $limiter->consume(1);
163+
$limit->wait();
164+
} while (!$limit->isAccepted());
165+
166+
// ...
167+
}
168+
169+
// ...
170+
}
171+
172+
Rate Limiter Storage and Locking
173+
--------------------------------
174+
175+
Rate limiters use the default cache and locking mechanisms defined in your
176+
Symfony application. If you prefer to change that, use the ``lock`` and
177+
``storage`` options:
178+
179+
.. code-block:: yaml
180+
181+
# config/packages/rate_limiter.yaml
182+
framework:
183+
rate_limiter:
184+
anonymous_api_limiter:
185+
# ...
186+
# the value is the name of any cache pool defined in your application
187+
cache_pool: 'app.redis_cache'
188+
# or define a service implementing StorageInterface to use a different
189+
# mechanism to store the limiter information
190+
storage: 'App\RateLimiter\CustomRedisStorage'
191+
# the value is the name of any lock defined in your application
192+
lock: 'app.rate_limiter_lock'
193+
194+
.. _`token bucket algorithm`: https://en.wikipedia.org/wiki/Token_bucket
195+
.. _`PHP date relative formats`: https://www.php.net/datetime.formats.relative

0 commit comments

Comments
 (0)