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)
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
A basic JSON-RPC implementation for your Flask-powered sites

Flask JSON-RPC A basic JSON-RPC implementation for your Flask-powered sites. Some reasons you might want to use: Simple, powerful, flexible and python

Cenobit Technologies 273 Dec 01, 2022
Cookiecutter API for creating Custom Skills for Azure Search using Python and Docker

cookiecutter-spacy-fastapi Python cookiecutter API for quick deployments of spaCy models with FastAPI Azure Search The API interface is compatible wit

Microsoft 379 Jan 03, 2023
This code generator creates FastAPI app from an openapi file.

fastapi-code-generator This code generator creates FastAPI app from an openapi file. This project is an experimental phase. fastapi-code-generator use

Koudai Aono 632 Jan 05, 2023
fastapi-mqtt is extension for MQTT protocol

fastapi-mqtt MQTT is a lightweight publish/subscribe messaging protocol designed for M2M (machine to machine) telemetry in low bandwidth environments.

Sabuhi 144 Dec 28, 2022
Easy and secure implementation of Azure AD for your FastAPI APIs 🔒

FastAPI-Azure-auth Azure AD Authentication for FastAPI apps made easy. 🚀 Description FastAPI is a modern, fast (high-performance), web framework for

Intility 216 Dec 27, 2022
Keycloack plugin for FastApi.

FastAPI Keycloack Keycloack plugin for FastApi. Your aplication receives the claims decoded from the access token. Usage Run keycloak on port 8080 and

Elber 4 Jun 24, 2022
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
A minimal Streamlit app showing how to launch and stop a FastAPI process on demand

Simple Streamlit + FastAPI Integration A minimal Streamlit app showing how to launch and stop a FastAPI process on demand. The FastAPI /run route simu

Arvindra 18 Jan 02, 2023
Single Page App with Flask and Vue.js

Developing a Single Page App with FastAPI and Vue.js Want to learn how to build this? Check out the post. Want to use this project? Build the images a

91 Jan 05, 2023
Ready-to-use and customizable users management for FastAPI

FastAPI Users Ready-to-use and customizable users management for FastAPI Documentation: https://fastapi-users.github.io/fastapi-users/ Source Code: ht

FastAPI Users 2.3k Dec 30, 2022
The template for building scalable web APIs based on FastAPI, Tortoise ORM and other.

FastAPI and Tortoise ORM. Powerful but simple template for web APIs w/ FastAPI (as web framework) and Tortoise-ORM (for working via database without h

prostomarkeloff 95 Jan 08, 2023
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
Cache-house - Caching tool for python, working with Redis single instance and Redis cluster mode

Caching tool for python, working with Redis single instance and Redis cluster mo

Tural 14 Jan 06, 2022
Restful Api developed with Flask using Prometheus and Grafana for monitoring and containerization with Docker :rocket:

Hephaestus 🚀 In Greek mythology, Hephaestus was either the son of Zeus and Hera or he was Hera's parthenogenous child. ... As a smithing god, Hephaes

Yasser Tahiri 16 Oct 07, 2022
京东图片点击验证码识别

京东图片验证码识别 本项目是@yqchilde 大佬的 JDMemberCloseAccount 识别图形验证码(#45)思路验证,若你也有思路可以提交Issue和PR也可以在 @yqchilde 的 TG群 找到我 声明 本脚本只是为了学习研究使用 本脚本除了采集处理验证码图片没有其他任何功能,也

AntonVanke 37 Dec 22, 2022
Keepalive - Discord Bot to keep threads from expiring

keepalive Discord Bot to keep threads from expiring Installation Create a new Di

Francesco Pierfederici 5 Mar 14, 2022
🚢 Docker images and utilities to power your Python APIs and help you ship faster. With support for Uvicorn, Gunicorn, Starlette, and FastAPI.

🚢 inboard 🐳 Docker images and utilities to power your Python APIs and help you ship faster. Description This repository provides Docker images and a

Brendon Smith 112 Dec 30, 2022
FastAPI-PostgreSQL-Celery-RabbitMQ-Redis bakcend with Docker containerization

FastAPI - PostgreSQL - Celery - Rabbitmq backend This source code implements the following architecture: All the required database endpoints are imple

Juan Esteban Aristizabal 54 Nov 26, 2022
Pagination support for flask

flask-paginate Pagination support for flask framework (study from will_paginate). It supports several css frameworks. It requires Python2.6+ as string

Lix Xu 264 Nov 07, 2022