File size: 4,042 Bytes
45df059
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4f1a78a
3371ef4
 
 
 
 
 
45df059
 
 
3371ef4
45df059
 
4f1a78a
45df059
4f1a78a
45df059
 
 
4f1a78a
 
45df059
4f1a78a
 
6f941c0
4f1a78a
 
 
45df059
 
4f1a78a
45df059
4f1a78a
6f941c0
45df059
 
4f1a78a
 
45df059
 
 
260a069
 
 
 
45df059
260a069
45df059
260a069
 
 
 
45df059
 
 
260a069
 
 
 
 
 
45df059
260a069
45df059
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import redis
import logging
import time
import re
from typing import Optional
from utils.config import config

logger = logging.getLogger(__name__)

class RedisClient:
    """Enhanced Redis client with proper connection handling"""
    
    _instance = None
    _redis_client = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(RedisClient, cls).__new__(cls)
        return cls._instance
    
    def __init__(self):
        if not hasattr(self, '_initialized'):
            self._initialized = True
            self._connect()
    
    def _connect(self):
        """Establish Redis connection with proper error handling for Redis Cloud"""
        logger.info(f"Attempting Redis connection with:")
        logger.info(f"  Host: {config.redis_host}")
        logger.info(f"  Port: {config.redis_port}")
        logger.info(f"  Username: {'SET' if config.redis_username else 'NOT SET'}")
        logger.info(f"  Password: {'SET' if config.redis_password else 'NOT SET'}")
        
        if not config.redis_host or config.redis_host == "localhost":
            logger.info("Redis not configured, skipping connection")
            return None

        try:
            host, port = self._parse_host_port(config.redis_host, config.redis_port)
            logger.info(f"Connecting to Redis Cloud at {host}:{port} with SSL")
            
            # Redis Cloud specific connection settings
            self._redis_client = redis.Redis(
                host=host,
                port=port,
                username=config.redis_username if config.redis_username else "default",
                password=config.redis_password,
                decode_responses=True,
                socket_connect_timeout=10,
                socket_timeout=10,
                ssl=True,
                ssl_cert_reqs=None,  # Important for Redis Cloud
                health_check_interval=30,
                retry_on_timeout=True
            )
            
            # Test connection
            self._redis_client.ping()
            logger.info("Successfully connected to Redis Cloud with SSL")
            return
            
        except Exception as e:
            logger.error(f"Redis Cloud SSL connection failed: {e}")
            self._redis_client = None
    
    def _parse_host_port(self, host_string: str, default_port: int) -> tuple:
        """Parse host and port from host string"""
        if not host_string:
            return "localhost", default_port
            
        # Remove any whitespace and control characters
        host_string = host_string.strip()
        host_string = re.sub(r'[\r\n\t\0]+', '', host_string)
        
        # Handle case where port is included in REDIS_HOST (e.g., "host:port")
        if ':' in host_string and not host_string.startswith('['):  # Not IPv6
            # Check if the part after : is a valid port number
            parts = host_string.rsplit(':', 1)
            try:
                host = parts[0]
                port = int(parts[1])
                # Validate that this looks like a port (0-65535)
                if 1 <= port <= 65535:
                    return host, port
                else:
                    # Invalid port, use default
                    return host_string, default_port
            except ValueError:
                # Port is not a valid integer, use default
                return host_string, default_port
        
        return host_string, default_port
    
    def get_client(self) -> Optional[redis.Redis]:
        """Get Redis client instance"""
        return self._redis_client
    
    def is_healthy(self) -> bool:
        """Check if Redis connection is healthy"""
        if not self._redis_client:
            return False
        try:
            self._redis_client.ping()
            return True
        except:
            return False
    
    def reconnect(self):
        """Reconnect to Redis"""
        self._connect()

# Global Redis client instance
redis_client = RedisClient()