Files
UnHided/mediaflow_proxy/utils/rate_limit_handlers.py
UrloMythus cfc6bbabc9 update
2026-02-19 20:15:03 +01:00

205 lines
6.2 KiB
Python

"""
Rate limit handlers for host-specific rate limiting strategies.
This module provides handler classes that implement specific rate limiting
logic for different streaming hosts (e.g., Vidoza's aggressive 509 rate limiting).
Similar pattern to stream_transformers.py but for rate limiting behavior.
"""
import logging
from typing import Optional
from urllib.parse import urlparse
logger = logging.getLogger(__name__)
class RateLimitHandler:
"""
Base class for rate limit handlers.
Subclasses should override properties to customize rate limiting behavior.
"""
@property
def cooldown_seconds(self) -> int:
"""
Duration in seconds to wait between upstream connections.
Default: 0 (no cooldown, allow immediate requests)
"""
return 0
@property
def use_head_cache(self) -> bool:
"""
Whether to cache HEAD responses to avoid upstream calls.
Default: False
"""
return False
@property
def use_stream_gate(self) -> bool:
"""
Whether to use distributed locking to serialize requests.
Default: False
"""
return False
@property
def exclusive_stream(self) -> bool:
"""
If True, the stream gate is held for the ENTIRE duration of the stream.
This prevents any concurrent connections to the same URL.
Required for hosts that 509 on ANY concurrent streams.
Default: False (gate released after headers received)
"""
return False
@property
def retry_after_seconds(self) -> int:
"""
Value for Retry-After header when returning 503.
Default: 2
"""
return 2
class VidozaRateLimitHandler(RateLimitHandler):
"""
Rate limit handler for Vidoza CDN.
Vidoza aggressively rate-limits (509) if ANY concurrent connections exist
to the same URL from the same IP. This handler:
- Uses EXCLUSIVE stream gate: only ONE stream at a time (gate held during entire stream)
- Caches HEAD responses to serve repeated probes without connections
- ExoPlayer/clients must wait for the current stream to finish before starting a new one
WARNING: This means only one client can actively stream at a time. Other clients will
wait (up to timeout) and eventually get 503 if the current stream is too long.
"""
@property
def cooldown_seconds(self) -> int:
return 0 # No cooldown needed - we use exclusive streaming instead
@property
def use_head_cache(self) -> bool:
return True
@property
def use_stream_gate(self) -> bool:
return True
@property
def exclusive_stream(self) -> bool:
"""
If True, the stream gate is held for the ENTIRE duration of the stream,
not just at the start. This prevents any concurrent connections.
Required for hosts like Vidoza that 509 on ANY concurrent connections.
"""
return True
@property
def retry_after_seconds(self) -> int:
return 5
class AggressiveRateLimitHandler(RateLimitHandler):
"""
Generic aggressive rate limit handler for hosts with strict rate limiting.
Use this for hosts that show similar behavior to Vidoza but may have
different thresholds.
"""
@property
def cooldown_seconds(self) -> int:
return 3
@property
def use_head_cache(self) -> bool:
return True
@property
def use_stream_gate(self) -> bool:
return True
@property
def retry_after_seconds(self) -> int:
return 2
# Registry of available rate limit handlers by ID
RATE_LIMIT_HANDLER_REGISTRY: dict[str, type[RateLimitHandler]] = {
"vidoza": VidozaRateLimitHandler,
"aggressive": AggressiveRateLimitHandler,
}
# Auto-detection: hostname patterns to handler IDs
# These patterns are checked against the video URL hostname
#
# NOTE: Vidoza CDN DOES rate limit concurrent connections from the same IP.
# When multiple clients request through the proxy, all requests come from
# the proxy's IP, triggering Vidoza's rate limit (509 errors).
# Stream-level rate limiting serializes requests to avoid this.
#
HOST_PATTERN_TO_HANDLER: dict[str, str] = {
"vidoza.net": "vidoza",
"vidoza.org": "vidoza",
# Add more patterns as needed for hosts that rate-limit CDN streaming:
# "example-cdn.com": "aggressive",
}
def get_rate_limit_handler(
handler_id: Optional[str] = None,
video_url: Optional[str] = None,
) -> RateLimitHandler:
"""
Get a rate limit handler instance.
Priority:
1. Explicit handler_id if provided
2. Auto-detect from video_url hostname
3. Default (no rate limiting)
Args:
handler_id: Explicit handler identifier (e.g., "vidoza", "aggressive")
video_url: Video URL for auto-detection based on hostname
Returns:
A rate limit handler instance. Returns base RateLimitHandler (no-op) if
no handler specified and no auto-detection match.
"""
# 1. Explicit handler ID
if handler_id:
handler_class = RATE_LIMIT_HANDLER_REGISTRY.get(handler_id)
if handler_class:
logger.debug(f"Using explicit rate limit handler: {handler_id}")
return handler_class()
else:
logger.warning(f"Unknown rate limit handler ID: {handler_id}")
# 2. Auto-detect from URL hostname
if video_url:
try:
hostname = urlparse(video_url).hostname or ""
# Check each pattern
for pattern, detected_handler_id in HOST_PATTERN_TO_HANDLER.items():
if pattern in hostname:
handler_class = RATE_LIMIT_HANDLER_REGISTRY.get(detected_handler_id)
if handler_class:
logger.info(f"[RateLimit] Auto-detected handler '{detected_handler_id}' for host: {hostname}")
return handler_class()
logger.debug(f"[RateLimit] No handler matched for hostname: {hostname}")
except Exception as e:
logger.warning(f"[RateLimit] Error during auto-detection: {e}")
# 3. Default: no rate limiting
return RateLimitHandler()
def get_available_handlers() -> list[str]:
"""Get list of available rate limit handler IDs."""
return list(RATE_LIMIT_HANDLER_REGISTRY.keys())