@@ -7,19 +7,43 @@ Rate Limiter
77 :doc: `experimental feature </contributing/code/experimental >`.
88
99A "rate limiter" controls how frequently some event (e.g. an HTTP request or a
10- login attempt) is allowed to happen. It implements a `token bucket algorithm `_,
11- which roughly works like this:
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.
13+
14+ Symfony uses these rate limiters in built-in features like "login throttling",
15+ which limits how many failed login attempts a user can make in a given period of
16+ time, but you can use them for your own features too.
17+
18+ Rate Limiting Strategies
19+ ------------------------
20+
21+ Symfony's rate limiter implements two of the most common strategies to enforce
22+ rate limits: **fixed window ** and **token bucket **.
23+
24+ Fixed Window Rate Limiter
25+ ~~~~~~~~~~~~~~~~~~~~~~~~~
26+
27+ This is the simplest technique and it's based on setting a limit for a given
28+ interval of time. For example: 5,000 requests per hour or 3 login attempts
29+ every 15 minutes.
30+
31+ Its main drawback is that resource usage is not evenly distributed in time. In
32+ the previous example, a user could make the 5,000 requests in the first minute
33+ (possibly overloading the server) and do nothing for the rest of the hour.
34+
35+ Token Bucket Rate Limiter
36+ ~~~~~~~~~~~~~~~~~~~~~~~~~
37+
38+ This technique implements the `token bucket algorithm `_, which defines a
39+ continuously updating budget of resource usage. It roughly works like this:
1240
1341* A bucket is initially created with zero or more tokens;
1442* A new token is added to the bucket with a predefined frequency (e.g. every second);
1543* Allowing an event consumes one or more tokens;
1644* If the bucket still contains tokens, the event is allowed; otherwise, it's denied;
1745* If the bucket is at full capacity, new tokens are discarded.
1846
19- Symfony uses these rate limiters in built-in features like "login throttling",
20- which limits how many failed login attempts a user can make in a given period of
21- time, but you can use them for your own features too.
22-
2347Installation
2448------------
2549
@@ -33,39 +57,32 @@ install the associated Symfony Component in your application:
3357 Configuration
3458-------------
3559
36- Use the following options to configure the behavior of the rate limiter:
60+ The following example creates two different rate limiters for an API service, to
61+ enforce different levels of service (free or paid):
3762
3863.. code-block :: yaml
3964
4065 # config/packages/rate_limiter.yaml
4166 framework :
4267 rate_limiter :
43- limiters :
44- api_requests :
45- # maximum number of tokens allowed in the bucket
46- limit : 100
47- # how many tokens are created and how often
48- rate :
49- amount : 100
50- interval : ' 1 hour'
51-
52- This example creates a rate limiter to restrict the number of HTTP requests of
53- some API to 100 per hour.
54-
55- There are other options that you can optionally define if needed:
56-
57- .. code-block :: yaml
58-
59- # config/packages/rate_limiter.yaml
60- framework :
61- rate_limiter :
62- limiters :
63- api_requests :
64- # ...
65- lock :
66- storage :
67- strategy :
68- interval :
68+ anonymous_api_limiter :
69+ strategy : fixed_window
70+ limit : 100
71+ interval : 60m
72+ authenticated_api_limiter :
73+ strategy : token_bucket
74+ limit : 5000
75+ rate : { interval: 1h, amount: 5000 }
76+
77+ In the ``anonymous_api_limiter ``, when you make the first HTTP request, you can
78+ make up to 100 requests in the next 60 minutes. After that time, the counter
79+ resets and you have another 100 requests for the following 60 minutes.
80+
81+ In the ``authenticated_api_limiter ``, when you make the first HTTP request you
82+ are allowed to make up to 5,000 HTTP requests in total, and this number grows
83+ at a rate of another 5,000 requests per hour. If you don't make that number of
84+ requests, the unused ones don't accumulate (the ``limit `` option prevents that
85+ number from being higher than 5,000).
6986
7087Rate Limiting in Action
7188-----------------------
@@ -84,11 +101,12 @@ the number of requests to the API::
84101
85102 class ApiController extends AbstractController
86103 {
87- public function index(LimiterInterface $limiter)
104+ // the variable name must be the exact same as the rate limiter name
105+ public function index(LimiterInterface $anonymous_api_limiter)
88106 {
89107 // the argument of consume() is the number of tokens to consume
90108 // and returns FALSE if they cannot be consumed
91- if (false === $limiter ->consume(1)) {
109+ if (false === $anonymous_api_limiter ->consume(1)) {
92110 throw new TooManyRequestsHttpException();
93111 }
94112
@@ -113,53 +131,34 @@ token is available. In those cases, use the ``reserve()`` and ``wait()`` methods
113131
114132 class ApiController extends AbstractController
115133 {
116- public function registerUser(LimiterInterface $limiter )
134+ public function registerUser(LimiterInterface $authenticated_api_limiter )
117135 {
118136 // this blocks the application until the given number of tokens can be consumed
119- $limiter ->reserve(1)->wait();
137+ $authenticated_api_limiter ->reserve(1)->wait();
120138
121139 // ...
122140 }
123141
124142 // ...
125143 }
126144
127- Creating Multiple Rate Limiters
128- -------------------------------
145+ Rate Limiter Storage and Locking
146+ --------------------------------
129147
130- You can define any number of rate limiters in the application. For example, you
131- could set different API request limits for anonymous and authenticated users:
148+ Rate limiters use the default cache and locking mechanisms defined in your
149+ Symfony application. If you prefer to change that, use the ``lock `` and
150+ ``storage `` options:
132151
133152.. code-block :: yaml
134153
135154 # config/packages/rate_limiter.yaml
136155 framework :
137156 rate_limiter :
138- limiters :
139- # 1 request per minute for anonymous users
140- anonymous_api_requests :
141- limit : 1
142- rate :
143- amount : 1
144- interval : ' 1 minute'
145- # 85 requests per minute for authenticated users
146- authenticated_api_requests :
147- limit : 85
148- rate :
149- amount : 85
150- interval : ' 1 minute'
151-
152- Now you can inject each rate limiter using the same name as in the configuration::
153-
154- // src/Controller/LuckyController.php
155- // ...
156-
157- class ApiController extends AbstractController
158- {
159- public function index(LimiterInterface $authenticated_api_requests)
160- {
161- // ...
162- }
163- }
157+ anonymous_api_limiter :
158+ # ...
159+ # the value is the name of any cache pool defined in your application
160+ storage : ' app.redis_cache'
161+ # the value is the name of any lock defined in your application
162+ lock : ' app.rate_limiter_lock'
164163
165164 .. _`token bucket algorithm` : https://en.wikipedia.org/wiki/Token_bucket
0 commit comments