A request rate limiter for fastapi

Overview

fastapi-limiter

pypi license workflows workflows

Introduction

FastAPI-Limiter is a rate limiting tool for fastapi routes.

Requirements

Install

Just install from pypi

> pip install fastapi-limiter

Quick Start

FastAPI-Limiter is simple to use, which just provide a dependency RateLimiter, the following example allow 2 times request per 5 seconds in route /.

import aioredis
import uvicorn
from fastapi import Depends, FastAPI

from fastapi_limiter import FastAPILimiter
from fastapi_limiter.depends import RateLimiter

app = FastAPI()


@app.on_event("startup")
async def startup():
    redis = await aioredis.create_redis_pool("redis://localhost")
    FastAPILimiter.init(redis)


@app.get("/", dependencies=[Depends(RateLimiter(times=2, seconds=5))])
async def index():
    return {"msg": "Hello World"}


if __name__ == "__main__":
    uvicorn.run("main:app", debug=True, reload=True)

Usage

There are some config in FastAPILimiter.init.

redis

The redis instance of aioredis.

prefix

Prefix of redis key.

identifier

Identifier of route limit, default is ip, you can override it such as userid and so on.

async def default_identifier(request: Request):
    forwarded = request.headers.get("X-Forwarded-For")
    if forwarded:
        return forwarded.split(",")[0]
    return request.client.host

callback

Callback when access is forbidden, default is raise HTTPException with 429 status code.

async def default_callback(request: Request, expire: int):
    """
    default callback when too many requests
    :param request:
    :param expire: The remaining seconds
    :return:
    """
    raise HTTPException(
        HTTP_429_TOO_MANY_REQUESTS, "Too Many Requests", headers={"Retry-After": str(expire)}
    )

License

This project is licensed under the Apache-2.0 License.

Comments
  • Rare failure mode

    Rare failure mode

    Looking at the core in fastapi_limiter/depends.py#L35-L42:

            p = redis.pipeline()
            p.incrby(key, 1)
            p.pttl(key)
            num, pexpire = await p.execute()
            if num == 1:
                await redis.pexpire(key, self.milliseconds)
            if num > self.times:
                return await callback(request, pexpire)
    

    In the extremely rare case that the process fails between incrementing the value with p.execute() and setting the ttl with redis.pexpire the key won't actually have a time to live set. The next request will increment the count, but as num will already be greater than 1 the expiry won't get set... so after self.times requests all following requests will count as exceeding the rate limit.

    opened by hardbyte 15
  • Multiple rate limiters does not work properly?

    Multiple rate limiters does not work properly?

    Using only 1 RateLimiter works fine, however multiple RateLimiters there are issues?

    Only the first RateLimiter is kind of working but the second one is not triggered?

    @router.get("/time", dependencies=[Depends(RateLimiter(times=3, seconds=5)), Depends(RateLimiter(times=5, hours=1))])
    

    The first RateLimiter only limits 2 times instead of 3 times and the second RateLimiter is not properly being checked until later on? Changing the amount of times will still have similar effects where you can only do less than the amount of times allowed.

    If you change it so that the RateLimiter has a greater time limit first, like this

    @router.get("/time", dependencies=[Depends(RateLimiter(times=10, hours=1)), Depends(RateLimiter(times=3, seconds=5))])
    

    This will only work for the first RateLimiter but not the second one.

    Edit: Whenever I try to change the path url for a specific route and restart the API, the route is still affected by the RateLimiter from before?

    I have to flush the data from the database to fix the issue.

    Edit 2: I guess that issue for why less calls happen is that it's calling the second rate limiter and incrementing if it passes?

    Edit 3: One possible idea for multiple RateLimiters could be something like this

    dependencies=[Depends(RaterLimiters(RateLimiter(...), RateLimiter(...)))]
    

    This class could grab each identifier, see if there are duplicates, if there are duplicates then sort then based on time then increment each unique identifier?

    opened by BookerLoL 6
  • [feature]Two RateLimiter's co-exist

    [feature]Two RateLimiter's co-exist

    Is it feasible to support two RateLimiter's at the same time? For instance,

    [Depends(RateLimiter(times=2, seconds=5)), Depends(RateLimiter(times=100, hours =24))]

    to limit max 2 hits every 5 seconds AND max 100 hits per day from the same ip. very much appreciated.

    enhancement 
    opened by zhiboz 4
  • Allow Rate Limiting within WebSockets

    Allow Rate Limiting within WebSockets

    This PR adds the capability to do rate limiting within websocket requests.

    For example (see examples/main.py):

    @app.websocket("/ws")
    async def websocket_endpoint(websocket: WebSocket):
         await websocket.accept()
         ratelimit = WebSocketRateLimiter(times=1, seconds=5)
         while True:
             try:
                 data = await websocket.receive_text()
                 await ratelimit(websocket, context_key=data) # NB: context_key is optional
                 await websocket.send_text(f"Hello, world")
             except WebSocketRateLimitException:
                 await websocket.send_text(f"Hello again")
    

    Context

    I was attempting to rate limit graphql requests. As graphql requests can come over a request or over a websocket, the conventional method of dependencies doesn't work. But the majority of the things we need to rate limit are already there - we have FastAPI with a redis client. All we have to do is construct the redis cache key a little differently.

    In order to support multiple rate strategies for a single route, currently in depends.py we iterate through the dependencies of the current route and store the index of the dependency which is then used in the rate limiting key. (See https://github.com/peterbraden/fastapi-limiter/commit/dd385d624c8d1000d9f3d2b335186cb6f638f1ab)

    In https://github.com/peterbraden/fastapi-limiter/blob/4e6c6fa6d8339bad459d801814c61735b658ae25/fastapi_limiter/depends.py#L32 the code assumes that the route has a dependencies list.

    In fact dependencies is optional in APIRoute and doesn't appear at all in APIWebSocketRoute.

    In a websocket none of this makes sense, we likely want to ratelimit more often than the lifetime of the websocket connection, and additionally we don't have connection scoped dependencies.

    Instead of trying to shoehorn this into the existing method, I've simply added a class WebSocketRateLimiter that derives from RateLimiter, and can be called directly within a websocket connection.

    opened by peterbraden 3
  • User-based Rate Limiting?

    User-based Rate Limiting?

    How do I enable different rates for each user? I know I can have a rate-limit for each user eg: 5 requests per 10 seconds. However, if I want each user to have their own rate limit eg:

    • user-1: 5 requests per 10 seconds.
    • user-2: 10 requests per 10 seconds.

    How do I do achieve different rate limits per user/ip?

    opened by Manas73 3
  • Support milliseconds

    Support milliseconds

    Thanks for the great contribution to FastApi!

    I was thinking it'd be a good idea to support milliseconds so that:

    1. fractions of seconds can be used, and
    2. it's the unit expiring information is stored https://redis.io/commands/expire#expires-and-persistence

    My first take is that milliseconds should be used as the default for all rate limiting but to avoid breaking changes I'd be happy to add a flag for seconds vs. milliseconds. Please let me know what you think!

    opened by rowrowrowrow 2
  • User customized rate limit ?

    User customized rate limit ?

    I want to have a subscription based api service. there are 3 types of subscription: Free: 10 api calls per 24hour Basic: 30 api calls per 24hour Advanced: 80 api calls per 24hour.

    Along with the request body, suppose the detaiils about the subscription is also available. Can such scenario be handled by your library ? I am using fastapi, and I would prefer using a library than writing my own rate limiter using reddis.

    opened by harshraj22 1
  • request: support aioredis>2.0

    request: support aioredis>2.0

    aioredis is undergoing massive codebase changes to become more stable, performant, and usable. Thus, the syntax has changed slightly regarding the create_pool call and evalsha methods. It'd be great for this library to support the upcoming release.

    opened by thearchitector 1
  • Rate Limit Bypass

    Rate Limit Bypass

    Just by sending X-Forwarded-For header with any random number or string with each request ex: X-Forwarded-For: 23189987 allows anyone to bypass the rate limiter no problem.

    opened by ErikASD 1
  • Access response in callback

    Access response in callback

    In some cases I'd like direct access to the response as well as the request in the callback.

    e.g. https://fastapi.tiangolo.com/advanced/response-change-status-code/

    opened by rowrowrowrow 1
  • Limits for different HTTP methods get merged together

    Limits for different HTTP methods get merged together

    Imagine I have some kind of a form and I want to configure 2 different limits: one for opening the form and the other for submitting it.

    @app.get("/", dependencies=[Depends(RateLimiter(times=10, seconds=5))])
    async def form_get():
        return {"msg": "Hello World"}
    
    
    @app.post("/", dependencies=[Depends(RateLimiter(times=1, seconds=5))])
    async def form_post():
        return {"msg": "Hello World"}
    

    Expected behaviour One could request the form 10 times in 5 seconds, and only after that would they get 429. However, even after that, they should be able to submit the form as they haven't made any POST requests yet.

    Actual behaviour After sending 1 GET request user would get 429 when trying to submit the form.

    See the commit in a forked repo with new test cases: https://github.com/vvkh/fastapi-limiter/commit/70338f968cc61649189a05cfe5fe2d7a43244dae

    opened by vvkh 0
  • NOSCRIPT No matching script. Please use EVAL error

    NOSCRIPT No matching script. Please use EVAL error

    Looking at FastAPI rate limiter v0.1.4 , we got an error after restarting Redis:

       File "/usr/local/lib/python3.8/site-packages/starlette/routing.py", line 52, in app
         response = await func(request)
       File "/usr/local/lib/python3.8/site-packages/fastapi/routing.py", line 204, in app
         solved_result = await solve_dependencies(
       File "/usr/local/lib/python3.8/site-packages/fastapi/dependencies/utils.py", line 548, in solve_dependencies
         solved = await call(**sub_values)
       File "/usr/local/lib/python3.8/site-packages/fastapi_limiter/depends.py", line 42, in __call__
         pexpire = await redis.evalsha(
     aioredis.errors.ReplyError: NOSCRIPT No matching script. Please use EVAL.
    

    fastapi-limiter==0.1.4 aioredis==1.3.1 fastapi==0.65.2

    We've never seen this error before, but think it is should be similar to:

    https://github.com/OptimalBits/bull/issues/1445

    opened by gtoonstra 2
  • evalsha() got an unexpected keyword argument 'keys'

    evalsha() got an unexpected keyword argument 'keys'

    HI: Fist of all, I want to appreciate that you build such awesome tool. When i use this library encounter some problem like title, which code write on

     File "/usr/local/lib/python3.8/site-packages/fastapi_limiter/depends.py", line 42, in __call__
        pexpire = await redis.evalsha(
    TypeError: evalsha() got an unexpected keyword argument 'keys'
    

    Environments:

    • python 3.8
    • aioredis == 2.0.0
    • fastapi-limiter==0.1.4
    • fastapi==0.68.1
    opened by PaiHsuehChung 9
  • Bug with latest version

    Bug with latest version

    Creating a new project with latest versions of fastapi and fastapi-limiter runs into an issue:

    TypeError: evalsha() got an unexpected keyword argument 'keys'
    

    I used the following test code:

    import aioredis
    from fastapi import Depends, FastAPI
    
    from fastapi_limiter import FastAPILimiter
    from fastapi_limiter.depends import RateLimiter
    
    app = FastAPI()
    
    @app.on_event("startup")
    async def startup():
        redis = await aioredis.from_url("redis://localhost", encoding="utf-8", decode_responses=True)
        await FastAPILimiter.init(redis)
    
    
    @app.get("/", dependencies=[Depends(RateLimiter(times=2, seconds=5))])
    async def index():
        return {"message": "Hello World"}
    
    
    opened by hardbyte 4
  • Feature: Added

    Feature: Added "enabled" as a feature to FastAPILimiter

    enabled: Default value is True which changes no features, but can be set to False. When False, no limiting checks will be done.

    Use Cases: For debugging other features of API, excluding the Rate Limiter For enabling/disabling Rate Limiter using an environment variable, for deployment purposes

    opened by trevorWieland 2
Releases(v0.1.5)
FastAPI Socket.io with first-class documentation using AsyncAPI

fastapi-sio Socket.io FastAPI integration library with first-class documentation using AsyncAPI The usage of the library is very familiar to the exper

Marián Hlaváč 9 Jan 02, 2023
FastAPI Auth Starter Project

This is a template for FastAPI that comes with authentication preconfigured.

Oluwaseyifunmi Oyefeso 6 Nov 13, 2022
Flood Detection with Google Earth Engine

ee-fastapi: Flood Detection System A ee-fastapi is a simple FastAPI web application for performing flood detection using Google Earth Engine in the ba

Cesar Aybar 69 Jan 06, 2023
Prometheus exporter for several chia node statistics

prometheus-chia-exporter Prometheus exporter for several chia node statistics It's assumed that the full node, the harvester and the wallet run on the

30 Sep 19, 2022
Flask-vs-FastAPI - Understanding Flask vs FastAPI Web Framework. A comparison of two different RestAPI frameworks.

Flask-vs-FastAPI Understanding Flask vs FastAPI Web Framework. A comparison of two different RestAPI frameworks. IntroductionIn Flask is a popular mic

Mithlesh Navlakhe 1 Jan 01, 2022
Code Specialist 27 Oct 16, 2022
A rate limiter for Starlette and FastAPI

SlowApi A rate limiting library for Starlette and FastAPI adapted from flask-limiter. Note: this is alpha quality code still, the API may change, and

Laurent Savaete 562 Jan 01, 2023
Full stack, modern web application generator. Using FastAPI, PostgreSQL as database, Docker, automatic HTTPS and more.

Full Stack FastAPI and PostgreSQL - Base Project Generator Generate a backend and frontend stack using Python, including interactive API documentation

Sebastián Ramírez 10.8k Jan 08, 2023
Drop-in MessagePack support for ASGI applications and frameworks

msgpack-asgi msgpack-asgi allows you to add automatic MessagePack content negotiation to ASGI applications (Starlette, FastAPI, Quart, etc.), with a s

Florimond Manca 128 Jan 02, 2023
implementation of deta base for FastAPIUsers

FastAPI Users - Database adapter for Deta Base Ready-to-use and customizable users management for FastAPI Documentation: https://fastapi-users.github.

2 Aug 15, 2022
FastAPI backend for Repost

Repost FastAPI This is the FastAPI implementation of the Repost API. Installation Python 3 must be installed and accessible through the use of a termi

PC 7 Jun 15, 2021
FastAPI CRUD template using Deta Base

Deta Base FastAPI CRUD FastAPI CRUD template using Deta Base Setup Install the requirements for the CRUD: pip3 install -r requirements.txt Add your D

Sebastian Ponce 2 Dec 15, 2021
cookiecutter template for web API with python

Python project template for Web API with cookiecutter What's this This provides the project template including minimum test/lint/typechecking package

Hitoshi Manabe 4 Jan 28, 2021
Dead-simple mailer micro-service for static websites

Mailer Dead-simple mailer micro-service for static websites A free and open-source software alternative to contact form services such as FormSpree, to

Romain Clement 42 Dec 21, 2022
Adds integration of the Chameleon template language to FastAPI.

fastapi-chameleon Adds integration of the Chameleon template language to FastAPI. If you are interested in Jinja instead, see the sister project: gith

Michael Kennedy 124 Nov 26, 2022
Lazy package to start your project using FastAPI✨

Fastapi-lazy 🦥 Utilities that you use in various projects made in FastAPI. Source Code: https://github.com/yezz123/fastapi-lazy Install the project:

Yasser Tahiri 95 Dec 29, 2022
A simple Redis Streams backed Chat app using Websockets, Asyncio and FastAPI/Starlette.

redis-streams-fastapi-chat A simple demo of Redis Streams backed Chat app using Websockets, Python Asyncio and FastAPI/Starlette. Requires Python vers

ludwig404 135 Dec 19, 2022
api versioning for fastapi web applications

fastapi-versioning api versioning for fastapi web applications Installation pip install fastapi-versioning Examples from fastapi import FastAPI from f

Dean Way 472 Jan 02, 2023
A rate limiter for Starlette and FastAPI

SlowApi A rate limiting library for Starlette and FastAPI adapted from flask-limiter. Note: this is alpha quality code still, the API may change, and

Laurent Savaete 562 Jan 01, 2023
A Python framework to build Slack apps in a flash with the latest platform features.

Bolt for Python A Python framework to build Slack apps in a flash with the latest platform features. Read the getting started guide and look at our co

SlackAPI 684 Jan 09, 2023