11import asyncio
22import pickle
3- from urllib .parse import urlparse
43
54try :
65 import aioredis
109from .asyncio_pubsub_manager import AsyncPubSubManager
1110
1211
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-
2812class AsyncRedisManager (AsyncPubSubManager ): # pragma: no cover
2913 """Redis based client manager for asyncio servers.
3014
3115 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.
3517
36- To use a Redis backend, initialize the :class:`Server ` instance as
18+ To use a Redis backend, initialize the :class:`AsyncServer ` instance as
3719 follows::
3820
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))
4124
4225 :param url: The connection URL for the Redis server. For a default Redis
4326 store running on the same host, use ``redis://``. To use an
@@ -47,62 +30,73 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover
4730 :param write_only: If set to ``True``, only initialize to emit events. The
4831 default of ``False`` initializes the class for emitting
4932 and receiving.
33+ :param redis_options: additional keyword arguments to be passed to
34+ ``aioredis.from_url()``.
5035 """
5136 name = 'aioredis'
5237
5338 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 ):
5540 if aioredis is None :
5641 raise RuntimeError ('Redis package is not installed '
5742 '(Run "pip install aioredis" in your '
5843 '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 ()
6449 super ().__init__ (channel = channel , write_only = write_only , logger = logger )
6550
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+
6656 async def _publish (self , data ):
6757 retry = True
6858 while True :
6959 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 :
7865 if retry :
7966 self ._get_logger ().error ('Cannot publish to redis... '
8067 'retrying' )
81- self .pub = None
8268 retry = False
8369 else :
8470 self ._get_logger ().error ('Cannot publish to redis... '
8571 'giving up' )
8672 break
8773
88- async def _listen (self ):
74+ async def _redis_listen_with_retries (self ):
8975 retry_sleep = 1
76+ connect = False
9077 while True :
9178 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 :
10186 self ._get_logger ().error ('Cannot receive from redis... '
10287 'retrying in '
10388 '{} secs' .format (retry_sleep ))
104- self . sub = None
89+ connect = True
10590 await asyncio .sleep (retry_sleep )
10691 retry_sleep *= 2
10792 if retry_sleep > 60 :
10893 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