1
1
import asyncio
2
2
import pickle
3
- from urllib .parse import urlparse
4
3
5
4
try :
6
5
import aioredis
10
9
from .asyncio_pubsub_manager import AsyncPubSubManager
11
10
12
11
13
- def _parse_redis_url (url ):
14
- p = urlparse (url )
15
- if p .scheme not in {'redis' , 'rediss' }:
16
- raise ValueError ('Invalid redis url' )
17
- ssl = p .scheme == 'rediss'
18
- host = p .hostname or 'localhost'
19
- port = p .port or 6379
20
- password = p .password
21
- if p .path :
22
- db = int (p .path [1 :])
23
- else :
24
- db = 0
25
- return host , port , password , db , ssl
26
-
27
-
28
12
class AsyncRedisManager (AsyncPubSubManager ): # pragma: no cover
29
13
"""Redis based client manager for asyncio servers.
30
14
31
15
This class implements a Redis backend for event sharing across multiple
32
- processes. Only kept here as one more example of how to build a custom
33
- backend, since the kombu backend is perfectly adequate to support a Redis
34
- message queue.
16
+ processes.
35
17
36
- To use a Redis backend, initialize the :class:`Server ` instance as
18
+ To use a Redis backend, initialize the :class:`AsyncServer ` instance as
37
19
follows::
38
20
39
- server = socketio.Server(client_manager=socketio.AsyncRedisManager(
40
- 'redis://hostname:port/0'))
21
+ url = 'redis://hostname:port/0'
22
+ server = socketio.AsyncServer(
23
+ client_manager=socketio.AsyncRedisManager(url))
41
24
42
25
:param url: The connection URL for the Redis server. For a default Redis
43
26
store running on the same host, use ``redis://``. To use an
@@ -47,62 +30,73 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover
47
30
:param write_only: If set to ``True``, only initialize to emit events. The
48
31
default of ``False`` initializes the class for emitting
49
32
and receiving.
33
+ :param redis_options: additional keyword arguments to be passed to
34
+ ``aioredis.from_url()``.
50
35
"""
51
36
name = 'aioredis'
52
37
53
38
def __init__ (self , url = 'redis://localhost:6379/0' , channel = 'socketio' ,
54
- write_only = False , logger = None ):
39
+ write_only = False , logger = None , redis_options = None ):
55
40
if aioredis is None :
56
41
raise RuntimeError ('Redis package is not installed '
57
42
'(Run "pip install aioredis" in your '
58
43
'virtualenv).' )
59
- (
60
- self . host , self . port , self . password , self . db , self . ssl
61
- ) = _parse_redis_url ( url )
62
- self .pub = None
63
- self .sub = None
44
+ if not hasattr ( aioredis . Redis , 'from_url' ):
45
+ raise RuntimeError ( 'Version 2 of aioredis package is required.' )
46
+ self . redis_url = url
47
+ self .redis_options = redis_options or {}
48
+ self ._redis_connect ()
64
49
super ().__init__ (channel = channel , write_only = write_only , logger = logger )
65
50
51
+ def _redis_connect (self ):
52
+ self .redis = aioredis .Redis .from_url (self .redis_url ,
53
+ ** self .redis_options )
54
+ self .pubsub = self .redis .pubsub ()
55
+
66
56
async def _publish (self , data ):
67
57
retry = True
68
58
while True :
69
59
try :
70
- if self .pub is None :
71
- self .pub = await aioredis .create_redis (
72
- (self .host , self .port ), db = self .db ,
73
- password = self .password , ssl = self .ssl
74
- )
75
- return await self .pub .publish (self .channel ,
76
- pickle .dumps (data ))
77
- except (aioredis .RedisError , OSError ):
60
+ if not retry :
61
+ self ._redis_connect ()
62
+ return await self .redis .publish (
63
+ self .channel , pickle .dumps (data ))
64
+ except aioredis .exceptions .RedisError :
78
65
if retry :
79
66
self ._get_logger ().error ('Cannot publish to redis... '
80
67
'retrying' )
81
- self .pub = None
82
68
retry = False
83
69
else :
84
70
self ._get_logger ().error ('Cannot publish to redis... '
85
71
'giving up' )
86
72
break
87
73
88
- async def _listen (self ):
74
+ async def _redis_listen_with_retries (self ):
89
75
retry_sleep = 1
76
+ connect = False
90
77
while True :
91
78
try :
92
- if self .sub is None :
93
- self .sub = await aioredis .create_redis (
94
- (self .host , self .port ), db = self .db ,
95
- password = self .password , ssl = self .ssl
96
- )
97
- self .ch = (await self .sub .subscribe (self .channel ))[0 ]
98
- retry_sleep = 1
99
- return await self .ch .get ()
100
- except (aioredis .RedisError , OSError ):
79
+ if connect :
80
+ self ._redis_connect ()
81
+ await self .pubsub .subscribe (self .channel )
82
+ retry_sleep = 1
83
+ async for message in self .pubsub .listen ():
84
+ yield message
85
+ except aioredis .exceptions .RedisError :
101
86
self ._get_logger ().error ('Cannot receive from redis... '
102
87
'retrying in '
103
88
'{} secs' .format (retry_sleep ))
104
- self . sub = None
89
+ connect = True
105
90
await asyncio .sleep (retry_sleep )
106
91
retry_sleep *= 2
107
92
if retry_sleep > 60 :
108
93
retry_sleep = 60
94
+
95
+ async def _listen (self ):
96
+ channel = self .channel .encode ('utf-8' )
97
+ await self .pubsub .subscribe (self .channel )
98
+ async for message in self ._redis_listen_with_retries ():
99
+ if message ['channel' ] == channel and \
100
+ message ['type' ] == 'message' and 'data' in message :
101
+ yield message ['data' ]
102
+ await self .pubsub .unsubscribe (self .channel )
0 commit comments