Skip to content

Commit

Permalink
✨(back) override channel_redis channel layer
Browse files Browse the repository at this point in the history
The channel layer provided by channel_redis use msgpack to serialize and
deserialize messages. We have a problem when a video is serialized. It
is impossible then to deserialize it because in the urls the resolution
can be used as key and those keys are integer. Msgpack is unable to
deserialize this kind of key. As suggested in the issue[1] open on
channel_redis git repository we implement our own serialize and
deserialize methods.
1: django/channels_redis#287
  • Loading branch information
lunika committed Jan 11, 2022
1 parent 0092e38 commit f447ecd
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/backend/marsha/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class Base(Configuration):
# }
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"BACKEND": "marsha.websocket.layers.JsonRedisChannelLayer",
"CONFIG": {
"hosts": values.ListValue(
[("redis", 6379)], environ_name="REDIS_HOST", environ_prefix=None
Expand Down
36 changes: 36 additions & 0 deletions src/backend/marsha/websocket/layers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Layers used by django channels"""
import json
import random

from django.core.serializers.json import DjangoJSONEncoder

from channels_redis.core import RedisChannelLayer


class JsonRedisChannelLayer(RedisChannelLayer):
"""Use json to serialize and deserialize messages."""

def serialize(self, message):
"""
Serializes message in json.
"""
value = bytes(json.dumps(message, cls=DjangoJSONEncoder), encoding="utf-8")
if self.crypter:
value = self.crypter.encrypt(value)

# As we use an sorted set to expire messages we need to guarantee uniqueness,
# with 12 bytes.
random_prefix = random.getrandbits(8 * 12).to_bytes(12, "big")
return random_prefix + value

def deserialize(self, message):
"""
Deserializes from a byte string.
"""
# Removes the random prefix
message = message[12:]
message = message.decode("utf-8")

if self.crypter:
message = self.crypter.decrypt(message, self.expiry + 10)
return json.loads(message)
31 changes: 31 additions & 0 deletions src/backend/marsha/websocket/tests/test_layers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Test marsha layers use by django channels."""
from django.test import TestCase

from marsha.websocket.layers import JsonRedisChannelLayer


class JsonRedisChannelLayerTest(TestCase):
"""Test serialize and deserialize."""

def test_serialize_message(self):
"""
Test default serialization method
"""
message = {"a": True, "b": None, "c": {"d": []}}
channel_layer = JsonRedisChannelLayer()
serialized = channel_layer.serialize(message)
self.assertIsInstance(serialized, bytes)
self.assertEqual(serialized[12:], b'{"a": true, "b": null, "c": {"d": []}}')

def test_deserialize_message(self):
"""
Test default deserialization method
"""
message = (
b'[\x85\xf8\xdeY\xe5\xa3}is\x0f3{"a": true, "b": null, "c": {"d": []}}'
)
channel_layer = JsonRedisChannelLayer()
deserialized = channel_layer.deserialize(message)

self.assertIsInstance(deserialized, dict)
self.assertEqual(deserialized, {"a": True, "b": None, "c": {"d": []}})

0 comments on commit f447ecd

Please sign in to comment.