@@ -124,20 +124,74 @@ Configuration
124
124
The following example creates two different rate limiters for an API service, to
125
125
enforce different levels of service (free or paid):
126
126
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
+ ]);
141
195
142
196
.. note ::
143
197
@@ -302,30 +356,147 @@ the :class:`Symfony\\Component\\RateLimiter\\Reservation` object returned by the
302
356
}
303
357
}
304
358
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
+ ]);
307
422
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::
313
424
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
+ ]);
326
496
327
497
.. _`DoS attacks` : https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
328
498
.. _`Apache mod_ratelimit` : https://httpd.apache.org/docs/current/mod/mod_ratelimit.html
329
499
.. _`NGINX rate limiting` : https://www.nginx.com/blog/rate-limiting-nginx/
330
500
.. _`token bucket algorithm` : https://en.wikipedia.org/wiki/Token_bucket
331
501
.. _`PHP date relative formats` : https://www.php.net/datetime.formats.relative
502
+ .. _`Race conditions` : https://en.wikipedia.org/wiki/Race_condition
0 commit comments