1
1
import logging
2
2
import pickle
3
3
import time
4
+ from urllib .parse import urlparse
4
5
5
6
try :
6
7
import redis
12
13
logger = logging .getLogger ('socketio' )
13
14
14
15
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
+
15
42
class RedisManager (PubSubManager ): # pragma: no cover
16
43
"""Redis based client manager.
17
44
@@ -27,15 +54,18 @@ class RedisManager(PubSubManager): # pragma: no cover
27
54
server = socketio.Server(client_manager=socketio.RedisManager(url))
28
55
29
56
: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``.
32
62
:param channel: The channel name on which the server sends and receives
33
63
notifications. Must be the same in all the servers.
34
64
:param write_only: If set to ``True``, only initialize to emit events. The
35
65
default of ``False`` initializes the class for emitting
36
66
and receiving.
37
67
:param redis_options: additional keyword arguments to be passed to
38
- ``Redis.from_url()``.
68
+ ``Redis.from_url()`` or ``Sentinel()`` .
39
69
"""
40
70
name = 'redis'
41
71
@@ -66,8 +96,16 @@ def initialize(self):
66
96
'with ' + self .server .async_mode )
67
97
68
98
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 )
71
109
self .pubsub = self .redis .pubsub (ignore_subscribe_messages = True )
72
110
73
111
def _publish (self , data ):
0 commit comments