Repository: django/channels

Issue:
Add ability to port http_session into websocket.receive
Since it's nice to have read/write access to the main HTTP session rather than just a `channel_session`, the session helpers should be extended to add the ability to persist the HTTP session key into the `channel_session` and have this be exposed as an attribute on the `message`.

Changes probably needed:
- New function to transfer http session key into channel session in a defined way
- Either `channel_session` or `http_session` decorator changed so it rehydrates the session from the key
- Roll calls to this stuff into the standard all-in-one decorators
- Make sure it's easily accessible from generic consumers

I would say that we should probably just make it part of the normal `channel_session` decorator, as it's going to be generally useful, and that way it'll appear wherever someone thinks it should.

(Based on #271)



Discussion:

Resolving patch:
diff --git a/channels/sessions.py b/channels/sessions.py
--- a/channels/sessions.py
+++ b/channels/sessions.py
@@ -183,3 +183,27 @@ def inner(message, *args, **kwargs):
             session.save()
         return result
     return inner
+
+
+def channel_and_http_session(func):
+    """
+    Enables both the channel_session and http_session.
+
+    Stores the http session key in the channel_session on websocket.connect messages.
+    It will then hydrate the http_session from that same key on subsequent messages.
+    """
+    @http_session
+    @channel_session
+    @functools.wraps(func)
+    def inner(message, *args, **kwargs):
+        # Store the session key in channel_session
+        if message.http_session is not None and settings.SESSION_COOKIE_NAME not in message.channel_session:
+            message.channel_session[settings.SESSION_COOKIE_NAME] = message.http_session.session_key
+        # Hydrate the http_session from session_key
+        elif message.http_session is None and settings.SESSION_COOKIE_NAME in message.channel_session:
+            session_engine = import_module(settings.SESSION_ENGINE)
+            session = session_engine.SessionStore(session_key=message.channel_session[settings.SESSION_COOKIE_NAME])
+            message.http_session = session
+        # Run the consumer
+        return func(message, *args, **kwargs)
+    return inner


Test patch:
diff --git a/channels/tests/test_sessions.py b/channels/tests/test_sessions.py
--- a/channels/tests/test_sessions.py
+++ b/channels/tests/test_sessions.py
@@ -3,7 +3,8 @@
 from django.conf import settings
 from django.test import override_settings
 from channels.message import Message
-from channels.sessions import channel_session, http_session, enforce_ordering, session_for_reply_channel
+from channels.sessions import channel_session, channel_and_http_session, http_session, enforce_ordering, \
+    session_for_reply_channel
 from channels.tests import ChannelTestCase
 from channels import DEFAULT_CHANNEL_LAYER, channel_layers
 
@@ -105,6 +106,41 @@ def inner(message):
         session2 = session_for_reply_channel("test-reply")
         self.assertEqual(session2["species"], "horse")
 
+    def test_channel_and_http_session(self):
+        """
+        Tests that channel_and_http_session decorator stores the http session key and hydrates it when expected
+        """
+        # Make a session to try against
+        session = session_for_reply_channel("test-reply-session")
+        # Construct message to send
+        message = Message({
+            "reply_channel": "test-reply-session",
+            "http_version": "1.1",
+            "method": "GET",
+            "path": "/test2/",
+            "headers": {
+                "host": b"example.com",
+                "cookie": ("%s=%s" % (settings.SESSION_COOKIE_NAME, session.session_key)).encode("ascii"),
+            },
+        }, None, None)
+
+        @channel_and_http_session
+        def inner(message):
+            pass
+
+        inner(message)
+
+        # It should store the session key
+        self.assertEqual(message.channel_session[settings.SESSION_COOKIE_NAME], session.session_key)
+
+        # Construct a new message
+        message2 = Message({"reply_channel": "test-reply-session", "path": "/"}, None, None)
+
+        inner(message2)
+
+        # It should hydrate the http_session
+        self.assertEqual(message2.http_session.session_key, session.session_key)
+
     def test_enforce_ordering_slight(self):
         """
         Tests that slight mode of enforce_ordering works