Skip to content

Commit e43d541

Browse files
committed
Merge branch '5.2' into 5.x
* 5.2: Minor rewords [RateLimiter][Security] More precisely document advanced rate limiter configuration
2 parents 6ae444f + 5a67b62 commit e43d541

File tree

4 files changed

+343
-42
lines changed

4 files changed

+343
-42
lines changed

cache.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ will create pools with service IDs that follow the pattern ``cache.[type]``.
183183
],
184184
]);
185185
186+
.. _cache-create-pools:
187+
186188
Creating Custom (Namespaced) Pools
187189
----------------------------------
188190

lock.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ processes asking for the same ``$version``::
228228
}
229229
}
230230

231+
.. _lock-named-locks:
232+
231233
Named Lock
232234
----------
233235

rate_limiter.rst

Lines changed: 204 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -124,20 +124,74 @@ Configuration
124124
The following example creates two different rate limiters for an API service, to
125125
enforce different levels of service (free or paid):
126126

127-
.. code-block:: yaml
128-
129-
# config/packages/rate_limiter.yaml
130-
framework:
131-
rate_limiter:
132-
anonymous_api:
133-
# use 'sliding_window' if you prefer that policy
134-
policy: 'fixed_window'
135-
limit: 100
136-
interval: '60 minutes'
137-
authenticated_api:
138-
policy: 'token_bucket'
139-
limit: 5000
140-
rate: { interval: '15 minutes', amount: 500 }
127+
.. configuration-block::
128+
129+
.. code-block:: yaml
130+
131+
# config/packages/rate_limiter.yaml
132+
framework:
133+
rate_limiter:
134+
anonymous_api:
135+
# use 'sliding_window' if you prefer that policy
136+
policy: 'fixed_window'
137+
limit: 100
138+
interval: '60 minutes'
139+
authenticated_api:
140+
policy: 'token_bucket'
141+
limit: 5000
142+
rate: { interval: '15 minutes', amount: 500 }
143+
144+
.. code-block:: xml
145+
146+
<!-- config/packages/rate_limiter.xml -->
147+
<?xml version="1.0" encoding="UTF-8" ?>
148+
<container xmlns="http://symfony.com/schema/dic/services"
149+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
150+
xmlns:framework="http://symfony.com/schema/dic/symfony"
151+
xsi:schemaLocation="http://symfony.com/schema/dic/services
152+
https://symfony.com/schema/dic/services/services-1.0.xsd
153+
http://symfony.com/schema/dic/symfony
154+
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
155+
156+
<framework:config>
157+
<framework:rate-limiter>
158+
<!-- policy: use 'sliding_window' if you prefer that policy -->
159+
<framework:limiter name="anonymous_api"
160+
policy="fixed_window"
161+
limit="100"
162+
interval="60 minutes"
163+
/>
164+
165+
<framework:limiter name="authenticated_api"
166+
policy="token_bucket"
167+
limit="5000"
168+
>
169+
<framework:rate interval="15 minutes"
170+
amount="500"
171+
/>
172+
</framework:limiter>
173+
</framework:rate-limiter>
174+
</framework:config>
175+
</container>
176+
177+
.. code-block:: php
178+
179+
// config/packages/rate_limiter.php
180+
$container->loadFromExtension('framework', [
181+
rate_limiter' => [
182+
'anonymous_api' => [
183+
// use 'sliding_window' if you prefer that policy
184+
'policy' => 'fixed_window',
185+
'limit' => 100,
186+
'interval' => '60 minutes',
187+
],
188+
'authenticated_api' => [
189+
'policy' => 'token_bucket',
190+
'limit' => 5000,
191+
'rate' => [ 'interval' => '15 minutes', 'amount' => 500 ],
192+
],
193+
],
194+
]);
141195
142196
.. note::
143197

@@ -302,30 +356,147 @@ the :class:`Symfony\\Component\\RateLimiter\\Reservation` object returned by the
302356
}
303357
}
304358

305-
Rate Limiter Storage and Locking
306-
--------------------------------
359+
Storing Rate Limiter State
360+
--------------------------
361+
362+
All rate limiter policies require to store their state(e.g. how many hits were
363+
already made in the current time window). By default, all limiters use the
364+
``cache.rate_limiter`` cache pool created with the :doc:`Cache component </cache>`.
365+
366+
Use the ``cache_pool`` option to override the cache used by a specific limiter
367+
(or even :ref:`create a new cache pool <cache-create-pools>` for it):
368+
369+
.. configuration-block::
370+
371+
.. code-block:: yaml
372+
373+
# config/packages/rate_limiter.yaml
374+
framework:
375+
rate_limiter:
376+
anonymous_api:
377+
# ...
378+
379+
# use the "cache.anonymous_rate_limiter" cache pool
380+
cache_pool: 'cache.anonymous_rate_limiter'
381+
382+
.. code-block:: xml
383+
384+
<!-- config/packages/rate_limiter.xml -->
385+
<?xml version="1.0" encoding="UTF-8" ?>
386+
<container xmlns="http://symfony.com/schema/dic/services"
387+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
388+
xmlns:framework="http://symfony.com/schema/dic/symfony"
389+
xsi:schemaLocation="http://symfony.com/schema/dic/services
390+
https://symfony.com/schema/dic/services/services-1.0.xsd
391+
http://symfony.com/schema/dic/symfony
392+
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
393+
394+
<framework:config>
395+
<framework:rate-limiter>
396+
<!-- cache-pool: use the "cache.anonymous_rate_limiter" cache pool -->
397+
<framework:limiter name="anonymous_api"
398+
policy="fixed_window"
399+
limit="100"
400+
interval="60 minutes"
401+
cache-pool="cache.anonymous_rate_limiter"
402+
/>
403+
404+
<!-- ... ->
405+
</framework:rate-limiter>
406+
</framework:config>
407+
</container>
408+
409+
.. code-block:: php
410+
411+
// config/packages/rate_limiter.php
412+
$container->loadFromExtension('framework', [
413+
rate_limiter' => [
414+
'anonymous_api' => [
415+
// ...
416+
417+
// use the "cache.anonymous_rate_limiter" cache pool
418+
'cache_pool' => 'cache.anonymous_rate_limiter',
419+
],
420+
],
421+
]);
307422
308-
Rate limiters use the default cache and locking mechanisms defined in your
309-
Symfony application. If you prefer to change that, use the ``lock_factory`` and
310-
``storage_service`` options:
311-
312-
.. code-block:: yaml
423+
.. note::
313424
314-
# config/packages/rate_limiter.yaml
315-
framework:
316-
rate_limiter:
317-
anonymous_api_limiter:
318-
# ...
319-
# the value is the name of any cache pool defined in your application
320-
cache_pool: 'app.redis_cache'
321-
# or define a service implementing StorageInterface to use a different
322-
# mechanism to store the limiter information
323-
storage_service: 'App\RateLimiter\CustomRedisStorage'
324-
# the value is the name of any lock defined in your application
325-
lock_factory: 'app.rate_limiter_lock'
425+
Instead of using the Cache component, you can also implement a custom
426+
storage. Create a PHP class that implements the
427+
:class:`Symfony\\Component\\RateLimiter\\Storage\\StorageInterface` and
428+
use the ``storage_service`` setting of each limiter to the service ID
429+
of this class.
430+
431+
Using Locks to Prevent Race Conditions
432+
--------------------------------------
433+
434+
`Race conditions`_ can happen when the same rate limiter is used by multiple
435+
simultaneous requests (e.g. three servers of a company hitting your API at the
436+
same time). Rate limiters use :doc:`locks </lock>` to protect their operations
437+
against these race conditions.
438+
439+
By default, Symfony uses the global lock configured by ``framework.lock``), but
440+
you can use a specific :ref:`named lock <lock-named-locks>` via the
441+
``lock_factory`` option:
442+
443+
.. configuration-block::
444+
445+
.. code-block:: yaml
446+
447+
# config/packages/rate_limiter.yaml
448+
framework:
449+
rate_limiter:
450+
anonymous_api:
451+
# ...
452+
453+
# use the "lock.rate_limiter.factory" for this limiter
454+
lock_factory: 'lock.rate_limiter.factory'
455+
456+
.. code-block:: xml
457+
458+
<!-- config/packages/rate_limiter.xml -->
459+
<?xml version="1.0" encoding="UTF-8" ?>
460+
<container xmlns="http://symfony.com/schema/dic/services"
461+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
462+
xmlns:framework="http://symfony.com/schema/dic/symfony"
463+
xsi:schemaLocation="http://symfony.com/schema/dic/services
464+
https://symfony.com/schema/dic/services/services-1.0.xsd
465+
http://symfony.com/schema/dic/symfony
466+
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
467+
468+
<framework:config>
469+
<framework:rate-limiter>
470+
<!-- limiter-factory: use the "lock.rate_limiter.factory" for this limiter -->
471+
<framework:limiter name="anonymous_api"
472+
policy="fixed_window"
473+
limit="100"
474+
interval="60 minutes"
475+
lock-factory="lock.rate_limiter.factory"
476+
/>
477+
478+
<!-- ... -->
479+
</framework:rate-limiter>
480+
</framework:config>
481+
</container>
482+
483+
.. code-block:: php
484+
485+
// config/packages/rate_limiter.php
486+
$container->loadFromExtension('framework', [
487+
rate_limiter' => [
488+
'anonymous_api' => [
489+
// ...
490+
491+
// use the "lock.rate_limiter.factory" for this limiter
492+
'lock_factory' => 'lock.rate_limiter.factory',
493+
],
494+
],
495+
]);
326496
327497
.. _`DoS attacks`: https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
328498
.. _`Apache mod_ratelimit`: https://httpd.apache.org/docs/current/mod/mod_ratelimit.html
329499
.. _`NGINX rate limiting`: https://www.nginx.com/blog/rate-limiting-nginx/
330500
.. _`token bucket algorithm`: https://en.wikipedia.org/wiki/Token_bucket
331501
.. _`PHP date relative formats`: https://www.php.net/datetime.formats.relative
502+
.. _`Race conditions`: https://en.wikipedia.org/wiki/Race_condition

0 commit comments

Comments
 (0)