From 804a4c561efcd21b52f9392ba649f678c2875d7a Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Mon, 13 Jul 2015 17:58:52 -0700 Subject: [PATCH] Implement send_channel_session --- channels/decorators.py | 34 ++++++++++++++++++++++++++++++++++ docs/getting-started.rst | 22 +++++++++++----------- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/channels/decorators.py b/channels/decorators.py index 584f66a..6012938 100644 --- a/channels/decorators.py +++ b/channels/decorators.py @@ -1,5 +1,8 @@ import functools +import hashlib +from importlib import import_module +from django.conf import settings from django.utils import six from channels import channel_backends, DEFAULT_CHANNEL_BACKEND @@ -24,3 +27,34 @@ def consumer(*channels, **kwargs): # TODO: Sessions, auth + +def send_channel_session(func): + """ + Provides a session-like object called "channel_session" to consumers + as a message attribute that will auto-persist across consumers with + the same incoming "send_channel" value. + """ + @functools.wraps(func) + def inner(*args, **kwargs): + # Make sure there's a send_channel in kwargs + if "send_channel" not in kwargs: + raise ValueError("No send_channel sent to consumer; this decorator can only be used on messages containing it.") + # Turn the send_channel into a valid session key length thing. + # We take the last 24 bytes verbatim, as these are the random section, + # and then hash the remaining ones onto the start, and add a prefix + # TODO: See if there's a better way of doing this + session_key = "skt" + hashlib.md5(kwargs['send_channel'][:-24]).hexdigest()[:8] + kwargs['send_channel'][-24:] + # Make a session storage + session_engine = import_module(settings.SESSION_ENGINE) + session = session_engine.SessionStore(session_key=session_key) + # If the session does not already exist, save to force our session key to be valid + if not session.exists(session.session_key): + session.save() + kwargs['channel_session'] = session + # Run the consumer + result = func(*args, **kwargs) + # Persist session if needed (won't be saved if error happens) + if session.modified: + session.save() + return result + return inner diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 6811b78..0f94062 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -275,7 +275,7 @@ However, that session is based on cookies, and so follows the user round the site - it's great for information that should persist across all WebSocket and HTTP connections, but not great for information that is specific to a single WebSocket (such as "which chatroom should this socket be connected to"). For -this reason, Channels also provides a ``websocker_channel_session`` decorator, +this reason, Channels also provides a ``send_channel_session`` decorator, which adds a ``channel_session`` attribute to the message; this works just like the normal ``session`` attribute, and persists to the same storage, but varies per-channel rather than per-cookie. @@ -284,10 +284,10 @@ Let's use it now to build a chat server that expects you to pass a chatroom name in the path of your WebSocket request (we'll ignore auth for now):: from channels import Channel - from channels.decorators import consumer, websocket_channel_session + from channels.decorators import consumer, send_channel_session @consumer("django.websocket.connect") - @websocket_channel_session + @send_channel_session def ws_connect(channel, send_channel, path, channel_session, **kwargs): # Work out room name from path (ignore slashes) room = path.strip("/") @@ -296,17 +296,17 @@ name in the path of your WebSocket request (we'll ignore auth for now):: Group("chat-%s" % room).add(send_channel) @consumer("django.websocket.keepalive") - @websocket_channel_session + @send_channel_session def ws_add(channel, send_channel, channel_session, **kwargs): Group("chat-%s" % channel_session['room']).add(send_channel) @consumer("django.websocket.receive") - @websocket_channel_session + @send_channel_session def ws_message(channel, send_channel, content, channel_session, **kwargs): Group("chat-%s" % channel_session['room']).send(content=content) @consumer("django.websocket.disconnect") - @websocket_channel_session + @send_channel_session def ws_disconnect(channel, send_channel, channel_session, **kwargs): Group("chat-%s" % channel_session['room']).discard(send_channel) @@ -340,7 +340,7 @@ Let's see what that looks like, assuming we have a ChatMessage model with ``message`` and ``room`` fields:: from channels import Channel - from channels.decorators import consumer, websocket_channel_session + from channels.decorators import consumer, send_channel_session from .models import ChatMessage @consumer("chat-messages") @@ -351,7 +351,7 @@ have a ChatMessage model with ``message`` and ``room`` fields:: Group("chat-%s" % room).send(message) @consumer("django.websocket.connect") - @websocket_channel_session + @send_channel_session def ws_connect(channel, send_channel, path, channel_session, **kwargs): # Work out room name from path (ignore slashes) room = path.strip("/") @@ -360,18 +360,18 @@ have a ChatMessage model with ``message`` and ``room`` fields:: Group("chat-%s" % room).add(send_channel) @consumer("django.websocket.keepalive") - @websocket_channel_session + @send_channel_session def ws_add(channel, send_channel, channel_session, **kwargs): Group("chat-%s" % channel_session['room']).add(send_channel) @consumer("django.websocket.receive") - @websocket_channel_session + @send_channel_session def ws_message(channel, send_channel, content, channel_session, **kwargs): # Stick the message onto the processing queue Channel("chat-messages").send(room=channel_session['room'], message=content) @consumer("django.websocket.disconnect") - @websocket_channel_session + @send_channel_session def ws_disconnect(channel, send_channel, channel_session, **kwargs): Group("chat-%s" % channel_session['room']).discard(send_channel)