fastapi-limiter

repository·main·Indexed 21 days ago

https://github.com/long2ice/fastapi-limiter

A request rate limiter for FastAPI version 0.2.0 that leverages pyrate-limiter for flexible request throttling. It provides the RateLimiter dependency for specific routes, RateLimiterMiddleware for global application-wide limiting, and WebSocketRateLimiter for controlling message frequency in WebSocket connections. Features include customizable request identifiers, custom callbacks for exceeded limits, and the ability to conditionally skip rate limiting.

Tokens
4.3K
Snippets
15
Records
16
Agent score
73%

What's inside fastapi-limiter

  1. Quick Start with RateLimiter dependency

    main

    To apply rate limiting to a specific route, use the RateLimiter dependency within the dependencies list of your route decorator. This example allows 2 requests every 5 seconds on the / route.

    import uvicorn
    from fastapi import Depends, FastAPI
    from pyrate_limiter import Duration, Limiter, Rate
    
    from fastapi_limiter.depends import RateLimiter
    
    app = FastAPI()
    
    @app.get(
        "/",
        dependencies=[Depends(RateLimiter(limiter=Limiter(Rate(2, Duration.SECOND * 5))))],
    )
    async def index():
        return {"msg": "Hello World"}
    
    
    if __name__ == "__main__":
        uvicorn.run("main:app", reload=True)
  2. Apply global rate limiting with RateLimiterMiddleware

    main

    To apply rate limiting to all routes in your application without adding dependencies to every route, use RateLimiterMiddleware. It accepts the same parameters as RateLimiter: limiter, identifier, callback, blocking, and skip.

    from fastapi import FastAPI
    from pyrate_limiter import Duration, Limiter, Rate
    from fastapi_limiter.middleware import RateLimiterMiddleware
    
    app = FastAPI()
    
    app.add_middleware(
        RateLimiterMiddleware,
        limiter=Limiter(Rate(2, Duration.SECOND * 5)),
    )
    
    @app.get("/")
    async def index():
        return {"msg": "Hello World"}
  3. Rate limiting within a WebSocket

    main

    Since WebSockets are long-lived, you should rate limit the data sent over the socket within the handler body using WebSocketRateLimiter. This allows you to control the frequency of incoming messages.

    from fastapi import WebSocket, HTTPException
    from fastapi_limiter.depends import WebSocketRateLimiter
    from pyrate_limiter import Duration, Limiter, Rate
    
    @app.websocket("/ws")
    async def websocket_endpoint(websocket: WebSocket):
        await websocket.accept()
        # Initialize the limiter inside the handler
        ratelimit = WebSocketRateLimiter(limiter=Limiter(Rate(1, Duration.SECOND * 5)))
        
        while True:
            try:
                data = await websocket.receive_text()
                # Call the limiter; context_key is optional
                await ratelimit(websocket, context_key=data)
                await websocket.send_text("Hello, world")
            except HTTPException:
                await websocket.send_text("Hello again")
  4. Apply multiple limiters to a single route

    main

    You can stack multiple RateLimiter dependencies on a single route to enforce different rate limits (e.g., a strict limit for short bursts and a broader limit for long durations).

    Note: Always place the stricter limiter (the one with the lower seconds/times ratio) first in the dependencies list.

    @app.get(
        "/multiple",
        dependencies=[
            Depends(RateLimiter(limiter=Limiter(Rate(1, Duration.SECOND * 5)))),
            Depends(RateLimiter(limiter=Limiter(Rate(2, Duration.SECOND * 15)))),
        ],
    )
    async def multiple():
        return {"msg": "Hello World"}
  5. Conditionally skip rate limiting

    main

    Use the skip parameter in RateLimiter to pass an async callable that determines if a specific request should bypass rate limiting. The callable receives the Request object and must return True to skip.

    from fastapi import Request
    from fastapi.depends import Depends
    from fastapi_limiter.depends import RateLimiter
    from pyrate_limiter import Duration, Limiter, Rate
    
    async def skip_logic(request: Request):
        return request.scope["path"] == "/skip"
    
    @app.get(
        "/skip",
        dependencies=[
            Depends(RateLimiter(limiter=Limiter(Rate(1, Duration.SECOND * 5)), skip=skip_logic))
        ],
    )
    async def skip_route():
        return {"This route skips rate limiting"}
  6. Customize the rate limit callback

    main

    You can override the default behavior (which raises a 429 error) by providing a custom callback function. This function is called when the rate limit is exceeded.

    from fastapi import HTTPException, status
    
    def custom_callback(*args, **kwargs):
        raise HTTPException(
            status.HTTP_429_TOO_MANY_REQUESTS,
            "Too Many Requests",
        )
  7. Customize the request identifier

    main

    By default, RateLimiter identifies requests using ip + path. You can provide a custom identifier callable to limit based on other criteria, such as a userid extracted from headers or tokens.

    from typing import Union
    from fastapi import Request
    from fastapi import WebSocket
    
    async def custom_identifier(request: Union[Request, WebSocket]):
        # Example: Extracting IP from X-Forwarded-For or client host
        forwarded = request.headers.get("X-Forwarded-For")
        if forwarded:
            ip = forwarded.split(",")[0]
        elif request.client:
            ip = request.client.host
        else:
            ip = "127.0.0.1"
        return ip + ":" + request.scope["path"]
  8. Configure RateLimiter parameters

    main

    The RateLimiter dependency accepts the following configuration parameters:

    • limiter: A pyrate_limiter.Limiter instance defining the rate limiting rules.
    • identifier: A callable to identify the request source (e.g., by user ID or IP). Defaults to ip + path.
    • callback: A callable invoked when the limit is exceeded. Defaults to raising an HTTPException with a 429 status code.
    • blocking: A boolean indicating whether to block the request when the limit is exceeded. Defaults to False.
    • skip: An async callable that takes a request and returns True to bypass rate limiting. Defaults to None.
  9. Skip rate limiting for specific requests

    main

    The RateLimiterMiddleware accepts a skip parameter. This should be an async function that takes a starlette.requests.Request and returns a boolean. If the function returns True, the middleware will call call_next(request) immediately, bypassing all rate limiting checks for that specific request.

    async def skip_admin_routes(request):
        # Skip rate limiting if the path starts with /admin
        return request.url.path.startswith("/admin")
    
    app.add_middleware(
        RateLimiterMiddleware,
        limiter=limiter,
        skip=skip_admin_routes
    )
  10. Use WebSocketRateLimiter for WebSockets

    main

    The WebSocketRateLimiter class is designed to enforce rate limits on WebSocket connections. It works similarly to RateLimiter but operates on WebSocket objects instead of Request/Response pairs.

    Parameters:

    • limiter: An instance of pyrate_limiter.Limiter.
    • identifier: An async function to extract the rate-limiting key from the WebSocket. Defaults to default_identifier.
    • callback: An async function called when the limit is reached. Defaults to default_callback.
    • blocking: If True, the limiter will block until a slot is available. Defaults to False.
    • skip: An async function that accepts a WebSocket and returns True if rate limiting should be bypassed.

    Note on Keys: The internal rate-limiting key is constructed as {rate_key}:{context_key}, where context_key is an optional string passed during the dependency call.

    from fastapi import FastAPI, WebSocket, Depends
    from fastapi_limiter.depends import WebSocketRateLimiter
    from pyrate_limiter import Limiter
    
    limiter = Limiter(rps=5)
    ws_limiter = WebSocketRateLimiter(limiter=limiter)
    
    @app.websocket("/ws")
    async def websocket_endpoint(websocket: WebSocket, limiter: WebSocketRateLimiter = Depends(ws_limiter)):
        # The limiter is called via Depends. 
        # Note: WebSocketRateLimiter.__call__ accepts (ws, context_key="")
        await websocket.accept()
        await websocket.send_text("Hello, world!")
  11. Use RateLimiterMiddleware to apply rate limiting to all requests

    main

    The RateLimiterMiddleware is a Starlette-based middleware that applies rate limiting to every incoming request in a FastAPI application. It uses a pyrate_limiter.Limiter instance to track request counts and can be configured with custom identifiers, callbacks for when limits are exceeded, and logic to skip certain requests.

    Parameters:

    • limiter: An instance of pyrate_limiter.Limiter used to manage the rate limiting logic.
    • identifier: An async function used to generate a unique key (e.g., based on IP or User ID) for the rate limit. Defaults to default_identifier.
    • callback: An async function called when a request exceeds the limit. Defaults to returning a 429 Too Many Requests JSON response.
    • blocking: A boolean indicating whether the limiter should block the execution when a limit is reached. Defaults to False.
    • skip: An async function that accepts a Request and returns a boolean. If it returns True, the rate limiting logic is bypassed for that request.
    from fastapi import FastAPI
    from pyrate_limiter import Limiter, BucketFullException
    from fastapi_limiter.middleware import RateLimiterMiddleware
    
    app = FastAPI()
    
    # Example setup
    limiter = Limiter(buckets=[...], default_rate_limit=...) 
    
    app.add_middleware(
        RateLimiterMiddleware,
        limiter=limiter,
        blocking=False,
        skip=my_skip_logic_function
    )