From 4370f043f7cbf5ab5b563b9ba9a3620cc7957750 Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Mon, 18 Jul 2016 23:24:28 -0400 Subject: [PATCH] Make group_send/demultiplex encode classmethods --- channels/binding/websockets.py | 11 ++++------- channels/generic/websockets.py | 23 +++++++++++++---------- docs/generics.rst | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/channels/binding/websockets.py b/channels/binding/websockets.py index 811f712..812b1ed 100644 --- a/channels/binding/websockets.py +++ b/channels/binding/websockets.py @@ -8,7 +8,8 @@ from ..generic.websockets import JsonWebsocketConsumer, WebsocketDemultiplexer class WebsocketBinding(Binding): """ - Websocket-specific outgoing binding subclass that uses JSON encoding. + Websocket-specific outgoing binding subclass that uses JSON encoding + and the built-in JSON/WebSocket multiplexer. To implement outbound, implement: - group_names, which returns a list of group names to send to @@ -26,7 +27,7 @@ class WebsocketBinding(Binding): model = None - # Optional stream multiplexing + # Stream multiplexing name stream = None @@ -40,11 +41,7 @@ class WebsocketBinding(Binding): } # Encode for the stream assert self.stream is not None - payload = WebsocketDemultiplexer.encode(self.stream, payload) - # Return WS format message - return { - "text": json.dumps(payload), - } + return WebsocketDemultiplexer.encode(self.stream, payload) def serialize_data(self, instance): """ diff --git a/channels/generic/websockets.py b/channels/generic/websockets.py index b002680..3f086ae 100644 --- a/channels/generic/websockets.py +++ b/channels/generic/websockets.py @@ -98,11 +98,12 @@ class WebsocketConsumer(BaseConsumer): else: raise ValueError("You must pass text or bytes") - def group_send(self, name, text=None, bytes=None): + @classmethod + def group_send(cls, name, text=None, bytes=None): if text is not None: - Group(name, channel_layer=self.message.channel_layer).send({"text": text}) + Group(name).send({"text": text}) elif bytes is not None: - Group(name, channel_layer=self.message.channel_layer).send({"bytes": bytes}) + Group(name).send({"bytes": bytes}) else: raise ValueError("You must pass text or bytes") @@ -153,8 +154,9 @@ class JsonWebsocketConsumer(WebsocketConsumer): """ super(JsonWebsocketConsumer, self).send(text=json.dumps(content)) - def group_send(self, name, content): - super(JsonWebsocketConsumer, self).group_send(name, json.dumps(content)) + @classmethod + def group_send(cls, name, content): + WebsocketConsumer.group_send(name, json.dumps(content)) class WebsocketDemultiplexer(JsonWebsocketConsumer): @@ -195,17 +197,18 @@ class WebsocketDemultiplexer(JsonWebsocketConsumer): raise ValueError("Invalid multiplexed frame received (no channel/payload key)") def send(self, stream, payload): - super(WebsocketDemultiplexer, self).send(self.encode(stream, payload)) + self.message.reply_channel.send(self.encode(stream, payload)) - def group_send(self, name, stream, payload): - super(WebsocketDemultiplexer, self).group_send(name, self.encode(stream, payload)) + @classmethod + def group_send(cls, name, stream, payload): + Group(name).send(cls.encode(stream, payload)) @classmethod def encode(cls, stream, payload): """ Encodes stream + payload for outbound sending. """ - return { + return {"text": json.dumps({ "stream": stream, "payload": payload, - } + })} diff --git a/docs/generics.rst b/docs/generics.rst index 326f0c0..8231a5f 100644 --- a/docs/generics.rst +++ b/docs/generics.rst @@ -175,6 +175,40 @@ that will do this for you if you call it explicitly. ``self.close()`` is also provided to easily close the WebSocket from the server end once you are done with it. +.. _multiplexing: + +WebSocket Multiplexing +---------------------- + +Channels provides a standard way to multiplex different data streams over +a single WebSocket, called a ``Demultiplexer``. You use it like this:: + + from channels.generic.websockets import WebsocketDemultiplexer + + class Demultiplexer(WebsocketDemultiplexer): + + mapping = { + "intval": "binding.intval", + "stats": "internal.stats", + } + +It expects JSON-formatted WebSocket frames with two keys, ``stream`` and +``payload``, and will match the ``stream`` against the mapping to find a +channel name. It will then forward the message onto that channel while +preserving ``reply_channel``, so you can hook consumers up to them directly +in the ``routing.py`` file, and use authentication decorators as you wish. + +You cannot use class-based consumers this way as the messages are no +longer in WebSocket format, though. If you need to do operations on +``connect`` or ``disconnect``, override those methods on the ``Demultiplexer`` +itself (you can also provide a ``connection_groups`` method, as it's just +based on the JSON WebSocket generic consumer). + +The :doc:`data binding ` code will also send out messages to clients +in the same format, and you can encode things in this format yourself by +using the ``WebsocketDemultiplexer.encode`` class method. + + Sessions and Users ------------------