feat: add rate_limit
This commit is contained in:
parent
da29380087
commit
21e19ed6a9
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
15
Back/tests/test_redis.py
Normal 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())
|
||||||
Loading…
Reference in New Issue
Block a user