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)
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 1, 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 154 Feb 16, 2021
Middleware for Starlette that allows you to store and access the context data of a request. Can be used with logging so logs automatically use request headers such as x-request-id or x-correlation-id.

starlette context Middleware for Starlette that allows you to store and access the context data of a request. Can be used with logging so logs automat

Tomasz Wójcik 300 Dec 26, 2022
Middleware for Starlette that allows you to store and access the context data of a request. Can be used with logging so logs automatically use request headers such as x-request-id or x-correlation-id.

starlette context Middleware for Starlette that allows you to store and access the context data of a request. Can be used with logging so logs automat

Tomasz Wójcik 110 Feb 16, 2021
A FastAPI Framework for things like Database, Redis, Logging, JWT Authentication and Rate Limits

A FastAPI Framework for things like Database, Redis, Logging, JWT Authentication and Rate Limits Install You can install this Library with: pip instal

Tert0 33 Nov 28, 2022
Farlimit - FastAPI rate limit with python

FastAPIRateLimit Contributing is F&E (free&easy) Y Usage pip install farlimit N

omid 27 Oct 6, 2022
Redis-based rate-limiting for FastAPI

Redis-based rate-limiting for FastAPI

Glib 6 Nov 14, 2022
:rocket: CLI tool for FastAPI. Generating new FastAPI projects & boilerplates made easy.

Project generator and manager for FastAPI. Source Code: View it on Github Features ?? Creates customizable project boilerplate. Creates customizable a

Yagiz Degirmenci 1k Jan 2, 2023
Simple FastAPI Example : Blog API using FastAPI : Beginner Friendly

fastapi_blog FastAPI : Simple Blog API with CRUD operation Steps to run the project: git clone https://github.com/mrAvi07/fastapi_blog.git cd fastapi-

Avinash Alanjkar 1 Oct 8, 2022
Пример использования GraphQL Ariadne с FastAPI и сравнение его с GraphQL Graphene FastAPI

FastAPI Ariadne Example Пример использования GraphQL Ariadne с FastAPI и сравнение его с GraphQL Graphene FastAPI - GitHub ###Запуск на локальном окру

ZeBrains Team 9 Nov 10, 2022
Sample-fastapi - A sample app using Fastapi that you can deploy on App Platform

Getting Started We provide a sample app using Fastapi that you can deploy on App

Erhan BÜTE 2 Jan 17, 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 1, 2022
FastAPI Server Session is a dependency-based extension for FastAPI that adds support for server-sided session management

FastAPI Server-sided Session FastAPI Server Session is a dependency-based extension for FastAPI that adds support for server-sided session management.

DevGuyAhnaf 5 Dec 23, 2022
fastapi-admin2 is an upgraded fastapi-admin, that supports ORM dialects, true Dependency Injection and extendability

FastAPI2 Admin Introduction fastapi-admin2 is an upgraded fastapi-admin, that supports ORM dialects, true Dependency Injection and extendability. Now

Glib 14 Dec 5, 2022
Code Specialist 27 Oct 16, 2022
Fastapi-ml-template - Fastapi ml template with python

FastAPI ML Template Run Web API Local $ sh run.sh # poetry run uvicorn app.mai

Yuki Okuda 29 Nov 20, 2022
FastAPI-Amis-Admin is a high-performance, efficient and easily extensible FastAPI admin framework. Inspired by django-admin, and has as many powerful functions as django-admin.

简体中文 | English 项目介绍 FastAPI-Amis-Admin fastapi-amis-admin是一个拥有高性能,高效率,易拓展的fastapi管理后台框架. 启发自Django-Admin,并且拥有不逊色于Django-Admin的强大功能. 源码 · 在线演示 · 文档 · 文

AmisAdmin 318 Dec 31, 2022
A dynamic FastAPI router that automatically creates CRUD routes for your models

⚡ Create CRUD routes with lighting speed ⚡ A dynamic FastAPI router that automatically creates CRUD routes for your models

Adam Watkins 950 Jan 8, 2023
Adds simple SQLAlchemy support to FastAPI

FastAPI-SQLAlchemy FastAPI-SQLAlchemy provides a simple integration between FastAPI and SQLAlchemy in your application. It gives access to useful help

Michael Freeborn 465 Jan 7, 2023