diff --git a/README.md b/README.md index e622831..b394d02 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,12 @@ Dans le code suivant, il y a plusieurs fonctions qui ont été implémenté dans Route: '/info/' Accès : Pas d'accès spécifique +4. Rate Limiting +La fonction rate_limit_middleware agit comme un middleware. Elle prend deux paramètres : request qui représente la +requête HTTP entrante et call_next qui est une fonction représentant le prochain middleware ou le gestionnaire de route réel dans le pipeline de traitement. +Cela nous permet de traiter les requêtes et en locurrence de les limiter afin de ne pas surcharger le server. Nous avons limimé les requêtes à 2 par opérations mathématique en 10 secondes. + + ### Opérations Mathématiques 1. Power Description : Calcul de la puissance d'un nombre @@ -121,3 +127,8 @@ FastAPI. En effet, lors du lancement de l'application, le programme app.py est e ajoutées tant que le décorateur n'est pas appelé, c'est-à-dire que les fonctions décorées ne sont pas exécutées une première fois. Il est alors nécessaire d'appeler chaque fonction dans le programme app afin de rendre utilisable leur route. + + +pip install python-multipart +pip install pydantic-settings + diff --git a/app.py b/app.py index e1fc4e4..c7a1cf0 100644 --- a/app.py +++ b/app.py @@ -10,6 +10,9 @@ from collections import Counter import os import time +from datetime import datetime, timedelta + +from functools import wraps # Load environment variables from a .env file if present load_dotenv() @@ -20,6 +23,7 @@ app = FastAPI(title=sett_Env.title, description=sett_Env.description) route_request_counter = Counter() route_time_counter = Counter() +rate_limiting = Counter() @app.on_event("shutdown") @@ -32,7 +36,6 @@ def shutdown_event(): except Exception as e: print(f"Error deleting statistics file: {e}") - def get_saved_values(): """ Retrieve the statistics data saved in the file @@ -47,7 +50,6 @@ def get_saved_values(): pickle.dump(values, file) return values - def save_value(values): """ Save the current API statistics in a file @@ -56,7 +58,6 @@ def save_value(values): with open("saved_count.pkl", "wb") as file: pickle.dump(values, file) - @app.middleware("http") async def count_requests(request: Request, call_next): """ @@ -73,7 +74,6 @@ async def count_requests(request: Request, call_next): route_time_counter[route] += round(end_time - start_time, 6) return response - def check_args_type(dict_args, type_args): """ Check if args in the dictionary corresponds to the expected type, raise an error if not @@ -99,7 +99,6 @@ def count_func_call(func): request_count[key_func] = 1 save_value(request_count) - def fast_api_decorator(route, method, type_args): def decorator(func): def wrapper(**kwargs): @@ -114,6 +113,26 @@ def wrapper(**kwargs): return wrapper return decorator +# Dictionary to store the last access time for each route +route_last_access = {} +@app.middleware("http") +async def rate_limit_middleware(request: Request, call_next): + route = request.url.path + + if route not in route_last_access: + route_last_access[route] = datetime.utcnow() + + time_difference = datetime.utcnow() - route_last_access[route] + + if time_difference < timedelta(minutes=1): + if route_last_access[route] > datetime.utcnow() - timedelta(seconds=10) and route_last_access[route] != datetime.min: + raise HTTPException(status_code=400, detail="Rate limit exceeded. Try again later.") + + route_last_access[route] = datetime.utcnow() + + response = await call_next(request) + return response + @fast_api_decorator(route="/power/", method=["GET"], type_args=[int, int]) def power_function(x: Annotated[int, Query(description="Int we'll compute the power")], @@ -220,7 +239,7 @@ async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]): @fast_api_decorator(route="/info/me", method=["GET"], type_args=[]) -async def info(): +def info(): """ Get information about the application.