Repository: miguelgrinberg/python-socketio

Issue:
AsyncRedisManager SSL Support
Is there a way to use SSL with the AsyncRedisManager?  This is possible with RedisManager by passing in a "rediss" scheme instead of a "redis" scheme, but I don't see a way to do it with the Async manager.

I have sort of patched in a way to do this just by adding an "ssl" option to `AsyncRedisManager.__init__()`, then passing it through to `aioredis.create_redis()` in `_publish` and `_listen`.  Happy to do a pull request if that's the right way to do this.

Discussion:
miguelgrinberg: Two comments:
- Could you fix the error from the build?
- Any chance you can also do the standard (i.e. non-asyncio) Redis manager?

Thanks!
dbanty: > * Could you fix the error from the build?

Done I think, waiting on Travis.

> * Any chance you can also do the standard (i.e. non-asyncio) Redis manager?

The standard manager already supports rediss I think.  It uses redis.Redis.from_url which supports the scheme ( see [here](https://github.com/andymccurdy/redis-py/blob/6e23760fefbdf27ec941a2c3ae1ec2657875ac31/redis/client.py#L605) )


miguelgrinberg: Ay yes, the URLs are fully handled by the Redis client for the non-asyncio case. Great.

Patch:
diff --git a/socketio/asyncio_redis_manager.py b/socketio/asyncio_redis_manager.py
index 2265937..21499c2 100644
--- a/socketio/asyncio_redis_manager.py
+++ b/socketio/asyncio_redis_manager.py
@@ -12,8 +12,9 @@ from .asyncio_pubsub_manager import AsyncPubSubManager
 
 def _parse_redis_url(url):
     p = urlparse(url)
-    if p.scheme != 'redis':
+    if p.scheme not in {'redis', 'rediss'}:
         raise ValueError('Invalid redis url')
+    ssl = p.scheme == 'rediss'
     host = p.hostname or 'localhost'
     port = p.port or 6379
     password = p.password
@@ -21,7 +22,7 @@ def _parse_redis_url(url):
         db = int(p.path[1:])
     else:
         db = 0
-    return host, port, password, db
+    return host, port, password, db, ssl
 
 
 class AsyncRedisManager(AsyncPubSubManager):  # pragma: no cover
@@ -39,7 +40,8 @@ class AsyncRedisManager(AsyncPubSubManager):  # pragma: no cover
             'redis://hostname:port/0'))
 
     :param url: The connection URL for the Redis server. For a default Redis
-                store running on the same host, use ``redis://``.
+                store running on the same host, use ``redis://``.  To use an
+                SSL connection, use ``rediss://``.
     :param channel: The channel name on which the server sends and receives
                     notifications. Must be the same in all the servers.
     :param write_only: If set ot ``True``, only initialize to emit events. The
@@ -54,7 +56,9 @@ class AsyncRedisManager(AsyncPubSubManager):  # pragma: no cover
             raise RuntimeError('Redis package is not installed '
                                '(Run "pip install aioredis" in your '
                                'virtualenv).')
-        self.host, self.port, self.password, self.db = _parse_redis_url(url)
+        (
+            self.host, self.port, self.password, self.db, self.ssl
+        ) = _parse_redis_url(url)
         self.pub = None
         self.sub = None
         super().__init__(channel=channel, write_only=write_only, logger=logger)
@@ -66,7 +70,8 @@ class AsyncRedisManager(AsyncPubSubManager):  # pragma: no cover
                 if self.pub is None:
                     self.pub = await aioredis.create_redis(
                         (self.host, self.port), db=self.db,
-                        password=self.password)
+                        password=self.password, ssl=self.ssl
+                    )
                 return await self.pub.publish(self.channel,
                                               pickle.dumps(data))
             except (aioredis.RedisError, OSError):
@@ -87,7 +92,8 @@ class AsyncRedisManager(AsyncPubSubManager):  # pragma: no cover
                 if self.sub is None:
                     self.sub = await aioredis.create_redis(
                         (self.host, self.port), db=self.db,
-                        password=self.password)
+                        password=self.password, ssl=self.ssl
+                    )
                 self.ch = (await self.sub.subscribe(self.channel))[0]
                 return await self.ch.get()
             except (aioredis.RedisError, OSError):


Test patch:
diff --git a/tests/asyncio/test_asyncio_redis_manager.py b/tests/asyncio/test_asyncio_redis_manager.py
index 02c12d6..cbcaf6a 100644
--- a/tests/asyncio/test_asyncio_redis_manager.py
+++ b/tests/asyncio/test_asyncio_redis_manager.py
@@ -8,37 +8,37 @@ from socketio import asyncio_redis_manager
 class TestAsyncRedisManager(unittest.TestCase):
     def test_default_url(self):
         self.assertEqual(asyncio_redis_manager._parse_redis_url('redis://'),
-                         ('localhost', 6379, None, 0))
+                         ('localhost', 6379, None, 0, False))
 
     def test_only_host_url(self):
         self.assertEqual(
             asyncio_redis_manager._parse_redis_url('redis://redis.host'),
-            ('redis.host', 6379, None, 0))
+            ('redis.host', 6379, None, 0, False))
 
     def test_no_db_url(self):
         self.assertEqual(
             asyncio_redis_manager._parse_redis_url('redis://redis.host:123/1'),
-            ('redis.host', 123, None, 1))
+            ('redis.host', 123, None, 1, False))
 
     def test_no_port_url(self):
         self.assertEqual(
             asyncio_redis_manager._parse_redis_url('redis://redis.host/1'),
-            ('redis.host', 6379, None, 1))
+            ('redis.host', 6379, None, 1, False))
 
     def test_password(self):
         self.assertEqual(
             asyncio_redis_manager._parse_redis_url('redis://:pw@redis.host/1'),
-            ('redis.host', 6379, 'pw', 1))
+            ('redis.host', 6379, 'pw', 1, False))
 
     def test_no_host_url(self):
         self.assertEqual(
             asyncio_redis_manager._parse_redis_url('redis://:123/1'),
-            ('localhost', 123, None, 1))
+            ('localhost', 123, None, 1, False))
 
     def test_no_host_password_url(self):
         self.assertEqual(
             asyncio_redis_manager._parse_redis_url('redis://:pw@:123/1'),
-            ('localhost', 123, 'pw', 1))
+            ('localhost', 123, 'pw', 1, False))
 
     def test_bad_port_url(self):
         self.assertRaises(ValueError, asyncio_redis_manager._parse_redis_url,
@@ -51,3 +51,9 @@ class TestAsyncRedisManager(unittest.TestCase):
     def test_bad_scheme_url(self):
         self.assertRaises(ValueError, asyncio_redis_manager._parse_redis_url,
                           'http://redis.host:123/1')
+
+    def test_ssl_scheme(self):
+        self.assertEqual(
+            asyncio_redis_manager._parse_redis_url('rediss://'),
+            ('localhost', 6379, None, 0, True)
+        )