feat: add rate_limit

This commit is contained in:
roai_linux 2026-03-28 15:37:02 +03:30
parent da29380087
commit 21e19ed6a9
5 changed files with 51 additions and 35 deletions

View File

@ -14,6 +14,8 @@ class Settings(BaseSettings):
DATABASE_URL: str DATABASE_URL: str
REDIS_URL: str REDIS_URL: str
REDIS_USERNAME: str
REDIS_PASSWORD: str
LIVEKIT_API_KEY: str LIVEKIT_API_KEY: str
LIVEKIT_API_SECRET: str LIVEKIT_API_SECRET: str

View File

@ -15,33 +15,22 @@ class RateLimiter:
self.scope = scope self.scope = scope
async def __call__(self, request: Request): async def __call__(self, request: Request):
# getting client ip
client_ip = request.client.host if request.client else "127.0.0.1" client_ip = request.client.host if request.client else "127.0.0.1"
# when project is in docker and behind nginx, the real ip is in the headers
real_ip = request.headers.get("x-real-ip", request.headers.get("x-forwarded-for", client_ip)) real_ip = request.headers.get("x-real-ip", request.headers.get("x-forwarded-for", client_ip))
# if there are multiple ips, take the first ip (the real user ip)
real_ip = real_ip.split(",")[0].strip() real_ip = real_ip.split(",")[0].strip()
# creating redis key based on scope
if self.scope == "global": if self.scope == "global":
# key for global limit (e.g., rate_limit:global:192.168.1.5)
key = f"rate_limit:global:{real_ip}" key = f"rate_limit:global:{real_ip}"
else: else:
# key for endpoint limit (e.g., rate_limit:endpoint:192.168.1.5:/admin/login)
path = request.scope["path"] path = request.scope["path"]
key = f"rate_limit:endpoint:{real_ip}:{path}" key = f"rate_limit:endpoint:{real_ip}:{path}"
# adding 1 to the number of requests for this ip in redis
current_count = await redis_client.incr(key) current_count = await redis_client.incr(key)
# if this is the first request in this time window, set the expiration time (TTL)
if current_count == 1: if current_count == 1:
await redis_client.expire(key, self.window_seconds) await redis_client.expire(key, self.window_seconds)
# if the number of requests exceeds the limit, access is blocked
if current_count > self.requests: if current_count > self.requests:
# penalty: if someone spams, the time they are blocked is extended from zero again
await redis_client.expire(key, self.window_seconds) await redis_client.expire(key, self.window_seconds)
raise HTTPException( raise HTTPException(

View File

@ -9,7 +9,10 @@ services:
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
restart: always restart: always
ports:
- "5432:5432"
networks:
- internal
healthcheck: healthcheck:
test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" ] test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" ]
interval: 5s interval: 5s
@ -22,9 +25,13 @@ services:
volumes: volumes:
- redis_data:/data - redis_data:/data
restart: always restart: always
command: redis-server --requirepass ${REDIS_PASSWORD}
ports:
- "6379:6379"
networks:
- internal
healthcheck: healthcheck:
test: [ "CMD", "redis-cli", "ping" ] test: [ "CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping" ]
interval: 5s interval: 5s
timeout: 3s timeout: 3s
retries: 5 retries: 5
@ -35,30 +42,22 @@ services:
ports: ports:
- "7780:7880" - "7780:7880"
- "7781:7881" - "7781:7881"
- "50000-50100:50000-50100/udp" - "51000-51100:51000-51100/udp"
env_file: env_file:
- .env - .env
volumes: volumes:
- ./livekit.yaml:/etc/livekit/livekit.yaml - ./livekit.yaml:/etc/livekit/livekit.yaml
command: [ "--config", "/etc/livekit/livekit.yaml", "--keys", "${LIVEKIT_API_KEY}: ${LIVEKIT_API_SECRET}" ] command: [ "--config", "/etc/livekit/livekit.yaml", "--keys", "${LIVEKIT_API_KEY}: ${LIVEKIT_API_SECRET}" ]
restart: always restart: always
networks:
pgadmin: - public
image: dpage/pgadmin4:latest - internal
container_name: neda_pgadmin healthcheck:
restart: always test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:7880/health" ] # بررسی سلامت LiveKit
ports: interval: 10s
- "5050:80" timeout: 5s
environment: retries: 3
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL} start_period: 10s
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
PGADMIN_CONFIG_SERVER_MODE: 'True'
PGADMIN_CONFIG_UPGRADE_CHECK_ENABLED: 'False'
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'
volumes:
- pgadmin_data:/var/lib/pgadmin
depends_on:
- postgres
pg_backup: pg_backup:
image: prodrigestivill/postgres-backup-local image: prodrigestivill/postgres-backup-local
@ -75,7 +74,8 @@ services:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
SCHEDULE: '@daily' SCHEDULE: '@daily'
BACKUP_KEEP_DAYS: 7 BACKUP_KEEP_DAYS: 7
networks:
- internal
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
@ -89,6 +89,9 @@ services:
- "./:/app" - "./:/app"
env_file: env_file:
- .env - .env
networks:
- public
- internal
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
@ -98,6 +101,13 @@ services:
condition: service_started condition: service_started
restart: always restart: always
networks:
public:
driver: bridge
internal:
driver: bridge
# internal: true
volumes: volumes:
postgres_data: postgres_data:
redis_data: redis_data:

View File

@ -2,8 +2,8 @@ port: 7880
rtc: rtc:
tcp_port: 7881 tcp_port: 7881
port_range_start: 50000 port_range_start: 51000
port_range_end: 50100 port_range_end: 51100
use_external_ip: false use_external_ip: false
# node_ip: "94.183.170.121" # node_ip: "94.183.170.121"

15
Back/tests/test_redis.py Normal file
View File

@ -0,0 +1,15 @@
import asyncio
import redis.asyncio as redis
from core.config import settings
async def test_redis():
print(f"Testing connection to: {settings.REDIS_URL}")
try:
client = redis.from_url(settings.REDIS_URL, decode_responses=True)
pong = await client.ping()
print(f"Ping successful: {pong}")
except Exception as e:
print(f"Connection failed: {e}")
if __name__ == "__main__":
asyncio.run(test_redis())