11import logging
22import pickle
33import time
4+ from urllib .parse import urlparse
45
56try :
67 import redis
1213logger = logging .getLogger ('socketio' )
1314
1415
16+ def parse_redis_sentinel_url (url ):
17+ """Parse a Redis Sentinel URL with the format:
18+ redis+sentinel://[:password]@host1:port1,host2:port2,.../db/service_name
19+ """
20+ parsed_url = urlparse (url )
21+ if parsed_url .scheme != 'redis+sentinel' :
22+ raise ValueError ('Invalid Redis Sentinel URL' )
23+ sentinels = []
24+ for host_port in parsed_url .netloc .split ('@' )[- 1 ].split (',' ):
25+ host , port = host_port .rsplit (':' , 1 )
26+ sentinels .append ((host , int (port )))
27+ kwargs = {}
28+ if parsed_url .username :
29+ kwargs ['username' ] = parsed_url .username
30+ if parsed_url .password :
31+ kwargs ['password' ] = parsed_url .password
32+ service_name = None
33+ if parsed_url .path :
34+ parts = parsed_url .path .split ('/' )
35+ if len (parts ) >= 2 and parts [1 ] != '' :
36+ kwargs ['db' ] = int (parts [1 ])
37+ if len (parts ) >= 3 and parts [2 ] != '' :
38+ service_name = parts [2 ]
39+ return sentinels , service_name , kwargs
40+
41+
1542class RedisManager (PubSubManager ): # pragma: no cover
1643 """Redis based client manager.
1744
@@ -27,15 +54,18 @@ class RedisManager(PubSubManager): # pragma: no cover
2754 server = socketio.Server(client_manager=socketio.RedisManager(url))
2855
2956 :param url: The connection URL for the Redis server. For a default Redis
30- store running on the same host, use ``redis://``. To use an
31- SSL connection, use ``rediss://``.
57+ store running on the same host, use ``redis://``. To use a
58+ TLS connection, use ``rediss://``. To use Redis Sentinel, use
59+ ``redis+sentinel://`` with a comma-separated list of hosts
60+ and the service name after the db in the URL path. Example:
61+ ``redis+sentinel://user:pw@host1:1234,host2:2345/0/myredis``.
3262 :param channel: The channel name on which the server sends and receives
3363 notifications. Must be the same in all the servers.
3464 :param write_only: If set to ``True``, only initialize to emit events. The
3565 default of ``False`` initializes the class for emitting
3666 and receiving.
3767 :param redis_options: additional keyword arguments to be passed to
38- ``Redis.from_url()``.
68+ ``Redis.from_url()`` or ``Sentinel()`` .
3969 """
4070 name = 'redis'
4171
@@ -66,8 +96,16 @@ def initialize(self):
6696 'with ' + self .server .async_mode )
6797
6898 def _redis_connect (self ):
69- self .redis = redis .Redis .from_url (self .redis_url ,
70- ** self .redis_options )
99+ if not self .redis_url .startswith ('redis+sentinel://' ):
100+ self .redis = redis .Redis .from_url (self .redis_url ,
101+ ** self .redis_options )
102+ else :
103+ sentinels , service_name , connection_kwargs = \
104+ parse_redis_sentinel_url (self .redis_url )
105+ kwargs = self .redis_options
106+ kwargs .update (connection_kwargs )
107+ sentinel = redis .sentinel .Sentinel (sentinels , ** kwargs )
108+ self .redis = sentinel .master_for (service_name or self .channel )
71109 self .pubsub = self .redis .pubsub (ignore_subscribe_messages = True )
72110
73111 def _publish (self , data ):
0 commit comments