django / channels

Developer-friendly asynchrony for Django
https://channels.readthedocs.io
BSD 3-Clause "New" or "Revised" License
6.11k stars 800 forks source link

Websocket close user #280

Closed kowsari closed 8 years ago

kowsari commented 8 years ago

Hi First of all I would like thank you for the wonderfully useful module that you have developed. This will provide alot of realtime functionality for us.

I would like to implement a presence module that allow me to keep track of exactly who is logged into the server and available to chat at any moment.

I am a channels newbie and my question is how to get the message.user info in a websocket_disconnect. I have added the channel_session_user_from_http decorator to my consumers.py but I seem to be getting Anonymous user. For me to keep track of logged in/out users I need to be able to get the user info for both connecton and disconnection from the server.

Thanks

AlexejStukov commented 8 years ago

Try setting http_user=True for your consumer (doc). Then you should be able to get the user from message or self.message.

kowsari commented 8 years ago

Thanks.

I do this in my consumers.py file now

from channels import Group
from channels.sessions import channel_session
from channels.auth import channel_session_user_from_http
from channels.generic.websockets import JsonWebsocketConsumer

from com.utils.logger import CustomLogger

class LassoConsumer(JsonWebsocketConsumer):

    # Set to True to automatically port users from HTTP cookies
    # (you don't need channel_session_user, this implies it)
    http_user = True

    # Set to True if you want them, else leave out
    strict_ordering = True
    slight_ordering = False

    def connection_groups(self, **kwargs):
        """
        Called to return the list of groups to automatically add/remove
        this connection to/from.
        """
        return ["test"]

    def connect(self, message, **kwargs):
        """
        Perform things on connection start
        """
        CustomLogger().CORE.info('Websocket_connect. user = %s, message = %s', message.user, message)
        # transfer_user(message.http_session, message.channel_session)
        Group("notifications").add(message.reply_channel)

    def receive(self, content, **kwargs):
        """
        Called when a message is received with decoded JSON content
        """
        # Simple echo
        self.send(content)

    def disconnect(self, message, **kwargs):
        """
        Perform things on connection close
        """
        CustomLogger().CORE.info('Websocket_disconnect. user = %s, message = %s', message.user, message)
        Group("notifications").discard(message.reply_channel)

and this in my routing.py


channel_routing = [
    route_class(LassoConsumer, path=r"^/")
#    "websocket.connect": "com.lassoapp.consumers.websocket_connect",
#    "websocket.keepalive": "com.lassoapp.consumers.websocket_keepalive",
#    "websocket.disconnect": "com.lassoapp.consumers.websocket_disconnect"
]

But now I am getting AnonymousUser all the time in connect and disconnect.

Thanks

andrewgodwin commented 8 years ago

Is your websocket on the same domain and port as your main site (this includes runserver and localhost)? The cookies won't carry across if it isn't.

kowsari commented 8 years ago

Yes. Same domain running locally.

When I was using functions with decorators instead of JSonWebsocketConsumer I would at least get the connect to have message.user poplulated and disconnect had AnoymousUser, but now I have anonymous user for both.

This is the code that I had before that was partially working

# Connected to websocket.connect and websocket.keepalive
@channel_session_user_from_http
def websocket_connect(message):
    logger.info('websocket_connect. message = %s', message)
    # transfer_user(message.http_session, message.channel_session)
    Group("notifications").add(message.reply_channel)

# Connected to websocket.keepalive
@channel_session
def websocket_keepalive(message):
    logger.info('websocket_keepalive. message = %s', message)
    Group("notifications").add(message.reply_channel)

# Connected to websocket.disconnect
@channel_session
def websocket_disconnect(message):
    logger.info('websocket_disconnect. message = %s', message)
    Group("notifications").discard(message.reply_channel)
andrewgodwin commented 8 years ago

Are you definitely on the most recent version of channels? http_user is very new.

kowsari commented 8 years ago

I am using channels==0.17.1

andrewgodwin commented 8 years ago

So, to be precise, where exactly are you seeing AnonymousUser? Are you referring to the log messages in connect and disconnect?

kowsari commented 8 years ago

Yes, exactly. In the lassouserconsumer class connect and disconnect methods message.user should have the correct user passed to it from the logged in user in the session?

andrewgodwin commented 8 years ago

It should, but if you're not seeing message.user in connect (where it's originally worked out and stored) then it's not the session that's the issue. Can you print out the value of message['headers'] inside your connect handler and paste it here so we can verify the cookies are coming through?

kowsari commented 8 years ago

I get this:

['origin', 'http://0.0.0.0:8001'], ['upgrade', 'websocket'], ['accept-language', 'en-US,en;q=0.8,fa;q=0.6'], ['accept-encoding', 'gzip, deflate, sdch'], ['sec-websocket-version', '13'], ['host', 'localhost:8000'], ['sec-websocket-key', 'tt9fN5y77UO7XbxKpfvDZQ=='], ['user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36'], ['dnt', '1'], ['connection', 'Upgrade'], ['cookie', 'djdt=hide; csrftoken=jz1v3jeYRn86wIzU5dfXCxw1XagBlVuM; token=11dc9e370d3a23b5a42e1b07810f1c43ee3aaede; __utma=111872281.1410338520.1470179296.1470371820.1470372156.6; __utmc=111872281; __utmz=111872281.1458667470.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)'], ['pragma', 'no-cache'], ['cache-control', 'no-cache'], ['sec-websocket-extensions', 'permessage-deflate; client_max_window_bits']]

Token is the DRF token and it seems like it is getting it passed thru.

If I lookup the callstack during connect I can see that it is being called from channel_session_user instead of http_session_user in channels/auth.py. This doesnt seem right.

andrewgodwin commented 8 years ago

Well, you're definitely not getting a session cookie through there (there'd be a sessionid cookie in the cookie headers), so that's why you're not seeing any sessions come through.

Are you sure the domain that you're serving sessions from and that you're connecting websockets to are EXACTLY the same? localhost is different to 127.0.0.1, and ports matter, so example.com and example.com:8000 do not share cookies.

kowsari commented 8 years ago

I checked and the domain are now exactly the same but I am still not getting the session id cookie. And now when tracking thru channels code I can see that it is not getting a session id. I will investigate that some more. However the strange this is that this was partially working when I was using decorators for webosockets rather than creating my own consumer class.

I am using DRF exclusively and use a token header, and I think that is not using the sessionid header. http://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication

kowsari commented 8 years ago

Hi, After an hour of messing with settings and trying different IP addresses, I was able to get this running on the mac using 127.0.0.1. Sorry for the inconvience and thanks for the help.

Now on to running this in nginx and uwsgi.