-
Notifications
You must be signed in to change notification settings - Fork 197
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
255 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -171,6 +171,7 @@ to 10, and all ``websocket.send!`` channels to 20: | |
If you want to enforce a matching order, use an ``OrderedDict`` as the | ||
argument; channels will then be matched in the order the dict provides them. | ||
|
||
.. _encryption | ||
``symmetric_encryption_keys`` | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
|
@@ -237,6 +238,44 @@ And then in your channels consumer, you can implement the handler: | |
async def redis_disconnect(self, *args): | ||
# Handle disconnect | ||
``serializer_format`` | ||
~~~~~~~~~~~~~~~~~~~~~~ | ||
By default every message which reach redis is encoded using `msgpack <https://msgpack.org/>`_. | ||
It is also possible to switch to `JSON <http://www.json.org/>`_: | ||
|
||
.. code-block:: python | ||
CHANNEL_LAYERS = { | ||
"default": { | ||
"BACKEND": "channels_redis.core.RedisChannelLayer", | ||
"CONFIG": { | ||
"hosts": ["redis://:[email protected]:6379/0"], | ||
"serializer_format": "json", | ||
}, | ||
}, | ||
} | ||
A new serializer may be registered (or can be overriden) by using ``channels_redis.serializers.registry``, | ||
providing a class which extends ``channels_redis.serializers.BaseMessageSerializer``, implementing ``dumps`` | ||
and ``loads`` methods, or which provides ``serialize``/``deserialize`` methods and calling the registration method on registry: | ||
|
||
.. code-block:: python | ||
from channels_redis.serializers import registry | ||
class MyFormatSerializer: | ||
def serialize(self, message): | ||
... | ||
def deserialize(self, message): | ||
... | ||
registry.register_serializer('myformat', MyFormatSerializer) | ||
**NOTE**: Serializers also perform the encryption job see *symmetric_encryption_keys*. | ||
|
||
|
||
Dependencies | ||
------------ | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import json | ||
import random | ||
import abc | ||
|
||
|
||
class SerializerDoesNotExist(KeyError): | ||
"""The requested serializer was not found.""" | ||
|
||
|
||
class BaseMessageSerializer(abc.ABC): | ||
|
||
def __init__( | ||
self, | ||
symmetric_encryption_keys=None, | ||
random_prefix_length=0, | ||
expiry=None, | ||
): | ||
self.random_prefix_length = random_prefix_length | ||
self.expiry = expiry | ||
# Set up any encryption objects | ||
self._setup_encryption(symmetric_encryption_keys) | ||
|
||
def _setup_encryption(self, symmetric_encryption_keys): | ||
# See if we can do encryption if they asked | ||
if symmetric_encryption_keys: | ||
if isinstance(symmetric_encryption_keys, (str, bytes)): | ||
raise ValueError( | ||
"symmetric_encryption_keys must be a list of possible keys" | ||
) | ||
try: | ||
from cryptography.fernet import MultiFernet | ||
except ImportError: | ||
raise ValueError( | ||
"Cannot run with encryption without 'cryptography' installed." | ||
) | ||
sub_fernets = [self.make_fernet(key) for key in symmetric_encryption_keys] | ||
self.crypter = MultiFernet(sub_fernets) | ||
else: | ||
self.crypter = None | ||
|
||
@abc.abstractmethod | ||
def dumps(self, message): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def loads(self, message): | ||
raise NotImplementedError | ||
|
||
def serialize(self, message): | ||
""" | ||
Serializes message to a byte string. | ||
""" | ||
message = self.dumps(message) | ||
# ensure message is bytes | ||
if isinstance(message, str): | ||
message = message.encode("utf-8") | ||
if self.crypter: | ||
message = self.crypter.encrypt(message) | ||
|
||
if self.random_prefix_length > 0: | ||
# provide random prefix | ||
message = ( | ||
random.getrandbits(8 * self.random_prefix_length).to_bytes( | ||
self.random_prefix_length, "big" | ||
) | ||
+ message | ||
) | ||
return message | ||
|
||
def deserialize(self, message): | ||
""" | ||
Deserializes from a byte string. | ||
""" | ||
if self.random_prefix_length > 0: | ||
# Removes the random prefix | ||
message = message[self.random_prefix_length :] # noqa: E203 | ||
|
||
if self.crypter: | ||
ttl = self.expiry if self.expiry is None else self.expiry + 10 | ||
message = self.crypter.decrypt(message, ttl) | ||
return self.loads(message) | ||
|
||
|
||
class MissingSerializer(BaseMessageSerializer): | ||
exception = None | ||
|
||
def __init__(self, *args, **kwargs): | ||
raise self.exception | ||
|
||
|
||
class JSONSerializer(BaseMessageSerializer): | ||
dumps = staticmethod(json.dumps) | ||
loads = staticmethod(json.loads) | ||
|
||
|
||
# code ready for a future in which msgpack may become an optional dependency | ||
try: | ||
import msgpack | ||
except ImportError as exc: | ||
|
||
class MsgPackSerializer(MissingSerializer): | ||
exception = exc | ||
|
||
else: | ||
|
||
class MsgPackSerializer(BaseMessageSerializer): | ||
dumps = staticmethod(msgpack.packb) | ||
loads = staticmethod(msgpack.unpackb) | ||
|
||
|
||
class SerializersRegistry: | ||
def __init__(self): | ||
self._registry = {} | ||
|
||
def register_serializer(self, format, serializer_class): | ||
""" | ||
Register a new serializer for given format | ||
""" | ||
assert isinstance(serializer_class, type) and ( | ||
issubclass(serializer_class, BaseMessageSerializer) | ||
or hasattr(serializer_class, "serialize") | ||
and hasattr(serializer_class, "deserialize") | ||
), """ | ||
`serializer_class` should be a class which implements `serialize` and `deserialize` method | ||
or a subclass of `channels_redis.serializers.BaseMessageSerializer` | ||
""" | ||
|
||
self._registry[format] = serializer_class | ||
|
||
def get_serializer(self, format, *args, **kwargs): | ||
try: | ||
serializer_class = self._registry[format] | ||
except KeyError: | ||
raise SerializerDoesNotExist(format) | ||
|
||
return serializer_class(*args, **kwargs) | ||
|
||
|
||
registry = SerializersRegistry() | ||
registry.register_serializer("json", JSONSerializer) | ||
registry.register_serializer("msgpack", MsgPackSerializer) |
Oops, something went wrong.