mirror of
https://github.com/laurentS/slowapi.git
synced 2026-03-13 09:10:20 +08:00
134 lines
4.6 KiB
Markdown
134 lines
4.6 KiB
Markdown
# Examples
|
|
|
|
Here are some examples of setup to get you started. Please open an issue if you have a use case that is not included here.
|
|
|
|
The tests show a lot of different use cases that are not all covered here.
|
|
|
|
## Apply a global (default) limit to all routes
|
|
|
|
```python
|
|
from starlette.applications import Starlette
|
|
from slowapi import Limiter, _rate_limit_exceeded_handler
|
|
from slowapi.util import get_remote_address
|
|
from slowapi.middleware import SlowAPIMiddleware
|
|
from slowapi.errors import RateLimitExceeded
|
|
|
|
limiter = Limiter(key_func=get_remote_address, default_limits=["1/minute"])
|
|
app = Starlette()
|
|
app.state.limiter = limiter
|
|
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
|
app.add_middleware(SlowAPIMiddleware)
|
|
|
|
# this will be limited by the default_limits
|
|
async def homepage(request: Request):
|
|
return PlainTextResponse("Only once per minute")
|
|
|
|
app.add_route("/home", homepage)
|
|
```
|
|
|
|
## Exempt a route from the global limit
|
|
|
|
```python
|
|
@app.route("/someroute")
|
|
@limiter.exempt
|
|
def t(request: Request):
|
|
return PlainTextResponse("I'm unlimited")
|
|
```
|
|
|
|
## Disable the limiter entirely
|
|
|
|
You might want to disable the limiter, for instance for testing, etc...
|
|
Note that this disables it entirely, for all users. It is essentially as if the limiter was not there.
|
|
Simply pass `enabled=False` to the constructor.
|
|
|
|
```python
|
|
limiter = Limiter(key_func=get_remote_address, enabled=False)
|
|
|
|
@app.route("/someroute")
|
|
@limiter.exempt
|
|
def t(request: Request):
|
|
return PlainTextResponse("I'm unlimited")
|
|
```
|
|
|
|
You can always switch this during the lifetime of the limiter:
|
|
|
|
```python
|
|
limiter.enabled = False
|
|
```
|
|
|
|
## Use redis as backend for the limiter
|
|
|
|
```python
|
|
limiter = Limiter(key_func=get_remote_address, storage_uri="redis://<host>:<port>/n")
|
|
```
|
|
|
|
where the /n in the redis url is the database number. To use the default one, just drop the /n from the url.
|
|
|
|
There are more examples in the [limits docs](https://limits.readthedocs.io/en/stable/storage.html) which is the library slowapi uses to manage storage.
|
|
|
|
## Set a custom cost per hit
|
|
|
|
Setting a custom cost per hit is useful to throttle requests based on something else than the request count.
|
|
|
|
Define a function which takes a request as parameter and returns a cost and pass it to the `limit` decorator:
|
|
|
|
```python
|
|
def get_hit_cost(request: Request) -> int:
|
|
return len(request)
|
|
|
|
@app.route("/someroute")
|
|
@limiter.limit("100/minute", cost=get_hit_cost)
|
|
def t(request: Request):
|
|
return PlainTextResponse("I'm limited by the request size")
|
|
```
|
|
|
|
## WSGI vs ASGI Middleware
|
|
|
|
`SlowAPIMiddleware` inheriting from Starlette's BaseHTTPMiddleware, you can find an alternative ASGI Middleware `SlowAPIASGIMiddleware`.
|
|
A few reasons to choose the ASGI middleware over the HTTP one are:
|
|
- Starlette [is probably going to deprecate BaseHTTPMiddleware](https://github.com/encode/starlette/issues/1678)
|
|
- ASGI middlewares [are more performant than WSGI ones](https://github.com/tiangolo/fastapi/issues/2241)
|
|
- built-in support for asynchronous exception handlers
|
|
- ...
|
|
|
|
|
|
Both middlewares are added to your application the same way:
|
|
```python
|
|
app = Starlette() # or FastAPI()
|
|
app.add_middleware(SlowAPIMiddleware)
|
|
```
|
|
or
|
|
```python
|
|
app = Starlette() # or FastAPI()
|
|
app.add_middleware(SlowAPIASGIMiddleware)
|
|
```
|
|
|
|
## Use view function's name instead of full endpoint as part of the storage key
|
|
|
|
Let's use this route as an example:
|
|
```python
|
|
@app.route("/some_route/{some_param}")
|
|
def my_func(some_param):
|
|
...
|
|
```
|
|
|
|
```python
|
|
limiter = Limiter(key_func=lambda: "mock", default_limits=["1/minute"], key_style="url")
|
|
```
|
|
|
|
When initializing the Limiter object with `key_style="url"`, it will use the full endpoint url as part of the storage key.
|
|
|
|
When calling the `/some_route/my_param` endpoint would result with a key shaped like: `LIMITER/mock//some_route/my_param/1/1/minute`.
|
|
|
|
> This means, that if the route contains some URL parameter, calling the endpoint with different parameters won't share the limitations.
|
|
|
|
```python
|
|
limiter = Limiter(key_func=lambda: "mock", default_limits=["1/minute"], key_style="endpoint")
|
|
```
|
|
|
|
When initializing the Limiter object with `key_style="endpoint"`, it will use the function name as part of the storage key.
|
|
|
|
When calling the `/some_route/my_param` endpoint would result with a key shaped like: `LIMITER/mock/{module}.my_func/1/1/minute`
|
|
|
|
> This means, that if the route contains some URL parameter, calling the endpoint with different parameters will still share the limitations, since the view function is the same.
|