Пример #1
0
class MessageHandler(WebSocketHandler):
    def __init__(self, *args, **kwargs):
        super(MessageHandler, self).__init__(*args, **kwargs)
        self.redis = Client()
        self.redis.connect()

    def get_current_user(self):
        user = self.get_secure_cookie("user")
        if user is None:
            return ''
        else:
            return user.strip('" ')

    def on_message(self, msg):
        msginfo = loads(msg)
        #listens for handshake from page
        if "user:" in msginfo['msg']:
            self.channel = msginfo['msg'].split(':')[1]
            #need to split the rest off to new func so it can be asynchronous
            self.listen()

    # Decorator turns the function into an asynchronous generator object
    @tornado.gen.engine
    def listen(self):
        #runs task given, with the yield required to get returned value
        #equivalent of callback/wait pairing from tornado.gen
        yield tornado.gen.Task(self.redis.subscribe, self.channel)
        if not self.redis.subscribed:
            self.write_message('ERROR IN SUBSCRIPTION')
        #listen from tornadoredis makes the listen object asynchronous
        #if using standard redis lib, it blocks while listening
        self.redis.listen(self.callback)
        # Try and fight race condition by loading from redis after listen
        # started need to use std redis lib because tornadoredis is in
        # subscribed state
        oldmessages = r_server.lrange(self.channel + ':messages', 0, -1)
        if oldmessages is not None:
            for message in oldmessages:
                self.write_message(message)

    def callback(self, msg):
        if msg.kind == 'message':
            self.write_message(str(msg.body))

    @tornado.gen.engine
    def on_close(self):
        yield tornado.gen.Task(self.redis.unsubscribe, self.channel)
        self.redis.disconnect()
Пример #2
0
class MessageHandler(WebSocketHandler):
    def __init__(self, *args, **kwargs):
        super(MessageHandler, self).__init__(*args, **kwargs)
        self.redis = Client()
        self.redis.connect()

    def get_current_user(self):
        user = self.get_secure_cookie("user")
        if user == None:
            return ''
        else:
            return user.strip('" ')

    def on_message(self, msg):
        msginfo = loads(msg)
        #listens for handshake from page
        if "user:" in msginfo['msg']:
            self.channel = msginfo['msg'].split(':')[1]
            #need to split the rest off to new func so it can be asynchronous
            self.listen()

    #decorator turns the function into an asynchronous generator object
    @tornado.gen.engine
    def listen(self):
        #runs task given, with the yield required to get returned value
        #equivalent of callback/wait pairing from tornado.gen
        yield tornado.gen.Task(self.redis.subscribe, self.channel)
        if not self.redis.subscribed:
            self.write_message('ERROR IN SUBSCRIPTION')
        #listen from tornadoredis makes the listen object asynchronous
        #if using standard redis lib, it blocks while listening
        self.redis.listen(self.callback)
        #try and fight race condition by loading from redis after listen started
        #need to use std redis lib because tornadoredis is in subscribed state
        oldmessages = r_server.lrange(self.channel + ':messages', 0, -1)
        if oldmessages != None:
            for message in oldmessages:
                self.write_message(message)

    def callback(self, msg):
        if msg.kind == 'message':
            self.write_message(str(msg.body))

    @tornado.gen.engine
    def on_close(self):
        yield tornado.gen.Task(self.redis.unsubscribe, self.channel)
        self.redis.disconnect()
Пример #3
0
class ChatHandler(WebSocketHandler):

    user = None
    chat = None
    chat_user = None
    socket_id = None
    redis_channels = ()
    ignore_next_message = False
    redis_client = None

    def check_origin(self, origin):
        return origin == config.get("app:main", "cherubplay.socket_origin")

    def open(self, chat_url):
        sockets.add(self)
        with db_session() as db:
            self.user = get_user(db, self.cookies)
            if self.user is None:
                self.close()
                return

            self.chat = get_chat(db, chat_url)
            if self.chat is None:
                self.close()
                return

            self.chat_user = get_chat_user(db, self.chat.id, self.user.id)
            if self.chat_user is None:
                self.close()
                return

            self.socket_id = str(uuid4())

            # Fire online message, but only if this is the only tab we have open.
            online_handles = online_user_store.online_handles(self.chat)
            if self.chat_user.handle not in online_handles:
                publish_client.publish(pubsub_channel(self.chat), json.dumps({
                    "action": "online",
                    "handle": self.chat_user.handle,
                }))

            # See if anyone else is online.
            online_handles_except_self = online_handles - {self.chat_user.handle}
            if len(online_handles_except_self) == 1:
                self.write_message({"action": "online", "handle": next(iter(online_handles_except_self))})
            elif len(online_handles_except_self) >= 1:
                self.write_message({
                    "action": "online_list",
                    "handles": sorted(list(online_handles)),
                })

            online_user_store.connect(self.chat_user, self.socket_id)

            self.redis_channels = (
                pubsub_channel(self.chat),
                pubsub_channel(self.user),
                pubsub_channel(self.chat_user),
            )

            self.redis_listen()
            # Send the backlog if necessary.
            if "after" in self.request.query_arguments:
                try:
                    after = int(self.request.query_arguments["after"][0])
                except ValueError:
                    return
                for message in (
                    db.query(Message)
                    .options(joinedload(Message.chat_user))
                    .filter(and_(Message.chat_id == self.chat.id, Message.id > after))
                ):
                    self.write_message({
                        "action": "message",
                        "message": {
                            "id":     message.id,
                            "type":   message.type.value,
                            "colour": message.colour,
                            "symbol": message.symbol_character,
                            "name":   message.chat_user.name if message.chat_user else None,
                            "text":   message.text.as_plain_text(),
                            "html":   message.text.as_html(),
                        }
                    })

    def on_message(self, message_string):
        message = json.loads(message_string)
        if message["action"] in ("typing", "stopped_typing"):
            publish_client.publish(pubsub_channel(self.chat), json.dumps({
                "action": message["action"],
                "handle": self.chat_user.handle,
            }))
            # Ignore our own typing messages.
            self.ignore_next_message = True

    def on_close(self):
        # Unsubscribe here and let the exit callback handle disconnecting.
        if self.redis_client:
            self.redis_client.unsubscribe(self.redis_channels)
        online_user_store.disconnect(self.chat, self.socket_id)
        # Fire offline message, but only if we don't have any other tabs open.
        if self.chat_user.handle not in online_user_store.online_handles(self.chat):
            publish_client.publish(pubsub_channel(self.chat), json.dumps({
                "action": "offline",
                "handle": self.chat_user.handle,
            }))
        sockets.remove(self)

    @engine
    def redis_listen(self):
        self.redis_client = Client(unix_socket_path=config.get("app:main", "cherubplay.socket_pubsub"))
        yield Task(self.redis_client.subscribe, self.redis_channels)
        self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe)

    def on_redis_message(self, message):
        if message.kind == "message":
            if message.body == "kicked":
                self.write_message("{\"action\":\"kicked\"}")
                self.close()
            elif not self.ignore_next_message:
                self.write_message(message.body)
            else:
                self.ignore_next_message = False

    def on_redis_unsubscribe(self, callback):
        self.redis_client.disconnect()
Пример #4
0
class ChatHandler(WebSocketHandler):

    def get_chat_user(self):
        return self.db.query(
            ChatUser, User, AnyChat,
        ).join(
            User, ChatUser.user_id == User.id,
        ).join(
            AnyChat, ChatUser.chat_id == AnyChat.id,
        ).filter(and_(
            ChatUser.user_id == self.user_id,
            ChatUser.chat_id == self.chat_id,
        )).one()

    def set_typing(self, is_typing):
        command = redis.sadd if is_typing else redis.srem
        typing_key = "chat:%s:typing" % self.chat_id
        if command(typing_key, self.user_number):
            redis.publish(self.channels["typing"], json.dumps({
                "typing": list(int(_) for _ in redis.smembers(typing_key)),
            }))

    def check_origin(self, origin):
        return origin_regex.match(origin) is not None

    def prepare(self):
        self.id = str(uuid4())
        self.joined = False
        try:
            self.session_id = self.cookies["session"].value
            self.chat_id = int(self.path_args[0])
            self.user_id = int(redis.get("session:%s" % self.session_id))
        except (KeyError, TypeError, ValueError):
            self.send_error(400)
            return
        self.db = sm()
        try:
            self.chat_user, self.user, self.chat = self.get_chat_user()
        except NoResultFound:
            self.send_error(404)
            return
        # Remember the user number so typing notifications can refer to it
        # without reopening the database session.
        self.user_number = self.chat_user.number
        self.user.last_online = datetime.now()
        self.user.last_ip = self.request.headers["X-Forwarded-For"]
        if self.user.group == "banned":
            self.send_error(403)
            return
        try:
            authorize_joining(redis, self.db, self)
        except (UnauthorizedException, BannedException, TooManyPeopleException):
            self.send_error(403)
            return

    def open(self, chat_id):
        sockets.add(self)
        redis.sadd("chat:%s:sockets:%s" % (self.chat_id, self.session_id), self.id)
        print "socket opened:", self.id, self.chat.url, self.user.username

        try:
            kick_check(redis, self)
        except KickedException:
            self.write_message(json.dumps({"exit": "kick"}))
            self.close()
            return

        # Subscribe
        self.channels = {
            "chat": "channel:%s" % self.chat_id,
            "user": "******" % (self.chat_id, self.user_id),
            "typing": "channel:%s:typing" % self.chat_id,
        }
        if self.chat.type == "pm":
            self.channels["pm"] = "channel:pm:%s" % self.user_id
        self.redis_listen()

        # Send backlog.
        try:
            after = int(self.request.query_arguments["after"][0])
        except (KeyError, IndexError, ValueError):
            after = 0
        messages = redis.zrangebyscore("chat:%s" % self.chat_id, "(%s" % after, "+inf")
        self.write_message(json.dumps({
            "chat": self.chat.to_dict(),
            "messages": [json.loads(_) for _ in messages],
        }))

        join_message_sent = join(redis, self.db, self)
        self.joined = True

        # Send userlist if nothing was sent by join().
        if not join_message_sent:
            self.write_message(json.dumps({"users": get_userlist(self.db, redis, self.chat)}))

        self.db.commit()

    def on_message(self, message):
        print "message:", message
        if message in ("typing", "stopped_typing"):
            self.set_typing(message == "typing")

    def on_close(self):
        # Unsubscribe here and let the exit callback handle disconnecting.
        if hasattr(self, "redis_client"):
            self.redis_client.unsubscribe(self.redis_client.subscribed)
        if self.joined and disconnect(redis, self.chat_id, self.id):
            try:
                send_quit_message(self.db, redis, *self.get_chat_user())
            except NoResultFound:
                send_userlist(self.db, redis, self.chat)
            self.db.commit()
        self.set_typing(False)
        print "socket closed:", self.id
        redis.srem("chat:%s:sockets:%s" % (self.chat_id, self.session_id), self.id)
        sockets.remove(self)

    def finish(self, *args, **kwargs):
        if hasattr(self, "db"):
            self.db.close()
            del self.db
        super(ChatHandler, self).finish(*args, **kwargs)

    @engine
    def redis_listen(self):
        self.redis_client = Client(
            host=os.environ["REDIS_HOST"],
            port=int(os.environ["REDIS_PORT"]),
            selected_db=int(os.environ["REDIS_DB"]),
        )
        yield Task(self.redis_client.subscribe, self.channels.values())
        self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe)

    def on_redis_message(self, message):
        print "redis message:", message
        if message.kind != "message":
            return
        self.write_message(message.body)
        if message.channel == self.channels["user"]:
            data = json.loads(message.body)
            if "exit" in data:
                self.joined = False
                self.close()

    def on_redis_unsubscribe(self, callback):
        self.redis_client.disconnect()
Пример #5
0
class ChatHandler(WebSocketHandler):

    def check_origin(self, origin):
        return True

    def open(self, chat_url):
        sockets.add(self)
        print chat_url
        self.user = get_user(self.cookies)
        if self.user is None:
            self.close()
            return
        self.chat = get_chat(chat_url)
        if self.chat is None:
            self.close()
            return
        self.chat_user = get_chat_user(self.chat.id, self.user.id)
        if self.chat_user is None:
            self.close()
            return
        self.socket_id = str(uuid4())
        # Fire online message, but only if this is the only tab we have open.
        online_symbols = set(int(_) for _ in publish_client.hvals("online:"+str(self.chat.id)))
        if self.chat_user.symbol not in online_symbols:
            publish_client.publish("chat:"+str(self.chat.id), json.dumps({
                "action": "online",
                "symbol": symbols[self.chat_user.symbol],
            }))
        # See if the other person is online.
        for symbol in online_symbols:
            if symbol == self.chat_user.symbol:
                continue
            self.write_message({
                "action": "online",
                "symbol": symbols[symbol],
            })
        publish_client.hset("online:"+str(self.chat.id), self.socket_id, self.chat_user.symbol)
        self.redis_listen()
        self.ignore_next_message = False
        # Send the backlog if necessary.
        if "after" in self.request.query_arguments:
            print "after"
            try:
                after = int(self.request.query_arguments["after"][0])
            except ValueError:
                return
            Session = sm()
            for message in Session.query(Message).filter(and_(
                Message.chat_id == self.chat.id,
                Message.id > after,
            )):
                print message
                self.write_message({
                    "action": "message",
                    "message": {
                        "id": message.id,
                        "type": message.type,
                        "colour": message.colour,
                        "symbol": symbols[message.symbol],
                        "text": message.text,
                    }
                })
            Session.commit()

    def on_message(self, message_string):
        message = json.loads(message_string)
        if message["action"] in ("typing", "stopped_typing"):
            publish_client.publish("chat:"+str(self.chat.id), json.dumps({
                "action": message["action"],
                "symbol": symbols[self.chat_user.symbol],
            }))
            # Ignore our own typing messages.
            self.ignore_next_message = True
        print message

    def on_close(self):
        # Unsubscribe here and let the exit callback handle disconnecting.
        self.redis_client.unsubscribe("chat:"+str(self.chat.id))
        publish_client.hdel("online:"+str(self.chat.id), self.socket_id)
        # Fire offline message, but only if we don't have any other tabs open.
        if str(self.chat_user.symbol) not in publish_client.hvals("online:"+str(self.chat.id)):
            publish_client.publish("chat:"+str(self.chat.id), json.dumps({
                "action": "offline",
                "symbol": symbols[self.chat_user.symbol],
            }))
        sockets.remove(self)

    @engine
    def redis_listen(self):
        self.redis_client = Client(unix_socket_path=config.get("app:main", "cherubplay.socket_pubsub"))
        yield Task(self.redis_client.subscribe, "chat:"+str(self.chat.id))
        self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe)

    def on_redis_message(self, message):
        if message.kind=="message":
            if not self.ignore_next_message:
                self.write_message(message.body)
                print "redis message:", message.body
            else:
                self.ignore_next_message = False

    def on_redis_unsubscribe(self, callback):
        self.redis_client.disconnect()