risunobushi commited on
Commit
eb48e62
·
1 Parent(s): c71d616

Enhance rate limiting with robust IP detection and cleanup mechanism

Browse files
Files changed (1) hide show
  1. app.py +66 -2
app.py CHANGED
@@ -14,6 +14,7 @@ import requests
14
  from dotenv import load_dotenv
15
  from PIL import Image
16
  from collections import defaultdict, deque
 
17
 
18
  import gradio as gr
19
  from supabase import create_client, Client
@@ -84,6 +85,38 @@ RATE_LIMIT_REQUESTS = 30
84
  RATE_LIMIT_WINDOW = 3600 # 1 hour in seconds
85
  request_tracker = defaultdict(deque)
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  def check_rate_limit(client_ip: str) -> bool:
88
  """Check if IP has exceeded rate limit. Returns True if allowed, False if blocked."""
89
  current_time = time.time()
@@ -97,8 +130,30 @@ def check_rate_limit(client_ip: str) -> bool:
97
  if len(user_requests) < RATE_LIMIT_REQUESTS:
98
  user_requests.append(current_time)
99
  return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
- return False
 
 
 
 
102
 
103
  # -----------------------------------------------------------------------------
104
  # Helper functions
@@ -227,7 +282,7 @@ def generate(base_img: Image.Image, garment_img: Image.Image, workflow_choice: s
227
  raise gr.Error("Please provide both images.")
228
 
229
  # Rate limiting check
230
- client_ip = request.client.host if request and request.client else "unknown"
231
  if not check_rate_limit(client_ip):
232
  raise gr.Error("Rate limit exceeded. Please try again later (30 requests per hour limit).")
233
 
@@ -462,6 +517,15 @@ with gr.Blocks(title="YOURMIRROR.IO - SM4LL-VTON Demo") as demo:
462
  product_example_2.select(lambda: Image.open("assets/product_image-2.jpg"), outputs=garment_in)
463
  product_example_3.select(lambda: Image.open("assets/product_image-3.jpg"), outputs=garment_in)
464
 
 
 
 
 
 
 
 
 
 
465
  # Run app if executed directly (e.g. `python app.py`). HF Spaces launches via
466
  # `python app.py` automatically if it finds `app.py` at repo root, but our file
467
  # lives in a sub-folder, so we keep the guard.
 
14
  from dotenv import load_dotenv
15
  from PIL import Image
16
  from collections import defaultdict, deque
17
+ import threading
18
 
19
  import gradio as gr
20
  from supabase import create_client, Client
 
85
  RATE_LIMIT_WINDOW = 3600 # 1 hour in seconds
86
  request_tracker = defaultdict(deque)
87
 
88
+ def get_client_ip(request: gr.Request) -> str:
89
+ """Extract client IP with multiple fallback methods."""
90
+ if not request:
91
+ return "no-request"
92
+
93
+ # Try multiple sources for IP address
94
+ ip_sources = [
95
+ # Direct client host
96
+ getattr(request.client, 'host', None) if hasattr(request, 'client') and request.client else None,
97
+ # Common proxy headers
98
+ request.headers.get('X-Forwarded-For', '').split(',')[0].strip() if hasattr(request, 'headers') else None,
99
+ request.headers.get('X-Real-IP', '') if hasattr(request, 'headers') else None,
100
+ request.headers.get('CF-Connecting-IP', '') if hasattr(request, 'headers') else None, # Cloudflare
101
+ request.headers.get('True-Client-IP', '') if hasattr(request, 'headers') else None,
102
+ request.headers.get('X-Client-IP', '') if hasattr(request, 'headers') else None,
103
+ ]
104
+
105
+ # Return first valid IP found
106
+ for ip in ip_sources:
107
+ if ip and ip.strip() and ip != '::1' and not ip.startswith('127.'):
108
+ return ip.strip()
109
+
110
+ # Final fallbacks
111
+ if hasattr(request, 'client') and request.client:
112
+ client_host = getattr(request.client, 'host', None)
113
+ if client_host:
114
+ return client_host
115
+
116
+ # If all else fails, use a session-based identifier
117
+ session_id = getattr(request, 'session_hash', 'unknown-session')
118
+ return f"session-{session_id}"
119
+
120
  def check_rate_limit(client_ip: str) -> bool:
121
  """Check if IP has exceeded rate limit. Returns True if allowed, False if blocked."""
122
  current_time = time.time()
 
130
  if len(user_requests) < RATE_LIMIT_REQUESTS:
131
  user_requests.append(current_time)
132
  return True
133
+ else:
134
+ # Log rate limit hit for monitoring
135
+ print(f"[RATE_LIMIT] IP {client_ip} exceeded limit ({len(user_requests)}/{RATE_LIMIT_REQUESTS})")
136
+ return False
137
+
138
+ def cleanup_rate_limiter():
139
+ """Periodic cleanup to prevent memory issues."""
140
+ current_time = time.time()
141
+ ips_to_remove = []
142
+
143
+ for ip, requests in request_tracker.items():
144
+ # Remove old requests
145
+ while requests and current_time - requests[0] > RATE_LIMIT_WINDOW:
146
+ requests.popleft()
147
+
148
+ # If no recent requests, mark IP for removal
149
+ if not requests:
150
+ ips_to_remove.append(ip)
151
 
152
+ # Clean up empty entries
153
+ for ip in ips_to_remove:
154
+ del request_tracker[ip]
155
+
156
+ print(f"[RATE_LIMITER] Cleaned up {len(ips_to_remove)} inactive IPs. Active IPs: {len(request_tracker)}")
157
 
158
  # -----------------------------------------------------------------------------
159
  # Helper functions
 
282
  raise gr.Error("Please provide both images.")
283
 
284
  # Rate limiting check
285
+ client_ip = get_client_ip(request)
286
  if not check_rate_limit(client_ip):
287
  raise gr.Error("Rate limit exceeded. Please try again later (30 requests per hour limit).")
288
 
 
517
  product_example_2.select(lambda: Image.open("assets/product_image-2.jpg"), outputs=garment_in)
518
  product_example_3.select(lambda: Image.open("assets/product_image-3.jpg"), outputs=garment_in)
519
 
520
+ # Periodic cleanup for rate limiter (runs every 10 minutes)
521
+ def periodic_cleanup():
522
+ cleanup_rate_limiter()
523
+ # Schedule next cleanup
524
+ threading.Timer(600.0, periodic_cleanup).start() # 10 minutes
525
+
526
+ # Start cleanup timer
527
+ threading.Timer(600.0, periodic_cleanup).start()
528
+
529
  # Run app if executed directly (e.g. `python app.py`). HF Spaces launches via
530
  # `python app.py` automatically if it finds `app.py` at repo root, but our file
531
  # lives in a sub-folder, so we keep the guard.