Example #1
0
 def listen(self):
     logging.info("begin to create pika client")
     routings = []
     routings.append(self.key)
     self.pika_client = PikaConsumer(self.key, 
                                     self, 
                                     exchange = "pclogin",
                                     exchange_type = "direct",
                                     tmp_queuename = self.key,
                                     tmp_routings = routings)
     self.pika_client.start()
Example #2
0
    def setuppubsub(self):
        #user = yield self._coll.find_one({"id":self._userid})
        #contacts = []
        #if user:
            #contacts = user.get("contacts", [])

        if self._closed:
            logging.info("websocket is already closed %s" % self._userid)
            return

        format_plat = ""
        #sub notify of specific platform
        if self._platform == "pc":
            format_plat = self._platform
            #pc only receive notify when online
            self._tmp_routings.append(self._userid)
            #some message only send to pc
            self._tmp_routings.append(self._platform + self._userid)
            #self._tmp_routings.extend(["pub" + x.get("id", "") for x in contacts])
        else:
            format_plat = "mob"
            self._queuename = self._userid
            self._routings.append(self._userid)
            #self._routings.extend(["pub" + x.get("id", "") for x in contacts])

        """
        gen temp routings to receive unreliable messages
        some notification is useful just at the time when it happened
        """
        time_random = datetime.utcnow().strftime('_%H%M%S%f')[:-3]
        self._tmpkey = time_random

        self._tmp_queuename = "temp%s%s%s" % (self._userid, time_random, format_plat)
        self._tmp_routings.append("temp%s" % self._userid)

        self._pikaclient = PikaConsumer(self._userid,
                                        self,
                                        exchange = 'user',
                                        exchange_type = 'direct',
                                        queuename = self._queuename,
                                        routings = self._routings,
                                        tmp_queuename = self._tmp_queuename,
                                        tmp_routings = self._tmp_routings)

        tornado.ioloop.IOLoop.instance().add_timeout(1000, self.startpika)
Example #3
0
class SubHandler(tornado.websocket.WebSocketHandler):
    MSGTYPE_IDENTIFY_RES = "identify_res"
    MSGTYPE_IDENTIFY_REQ = "identify_req"
    MSGTYPE_NOTIFY_REQ = "notify_req"
    MSGTYPE_NOTIFY_RES = "notify_res"
    MSGTYPE_PUB_REQ = "pub_req"
    MSGTYPE_PUB_RES = "pub_res"
    MSGTYPE_HEARBEAT_RES = "heartbeat_res"
    MSGTYPE_HEARBEAT_REQ = "heartbeat"

    MSGTYPE_HTTP_REQ = "http_req"

    MSGTYPE_INVALID = "error"
    KEY_TYPE = "type"
    KEY_PLAT = "platform"
    KEY_TOKEN = "token"
    KEY_IOS   = "devicetoken"

    def initialize(self):
        self._authed = False
        self._pikaclient = None
        self._messages = {}
        self._userid = None
        self._timeout = 0
        self._iostoken = None
        self._coll = self.application.db.users
        self._token = None
        self._pchecked = None
        
        #initial the websocket message handlers
        self._handlers = {}

        #initial the subscribe keys
        self._platform = None
        self._queuename = None
        self._tmp_queuename = None
        self._routings = []
        self._tmp_routings = []
        self._tmpkey = None
        self._closed = False

    def check_origin(self, origin):
        return True

    def checktimeout(self):
        logging.info("begin to handle timeout userid = %s tmpkey = %s" % (self._userid, self._tmpkey))
        self._timeout += 1

        if self._timeout > 5:
            self.close_ws()
            return True

        return False

    def open(self):
        tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 10, self.authcheck)
        self.application.websockets.append(self)

    def authcheck(self):
        if not self._userid:
            logging.error("auth timeout close socket")
            self.close_ws()
        else:
            logging.debug("already authed")

    def pro_check(self):
        logging.debug("begin to check  _userid = %s" % self._userid)
        mickey.groups.checkmember(self._token, self._userid)

    def connected(self):
        if self._pchecked:
            return

        logging.debug("begin to start provision timer = %s" % self._userid)
        self._pchecked = "true"
        tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 2, self.pro_check)

    def sendauthresponse(self, code):
        logging.debug("sendauthresponse _userid = %s" % self._userid)
        
        response = {}
        response[self.KEY_TYPE] = self.MSGTYPE_IDENTIFY_RES

        re_code = None
        if code == 412 or code == 498:
            re_code = code
        else:
            re_code = 525

        if not self._userid:
            response["response"] = {
                "result":"fail",
                "code":str(re_code)
            }

            self.write_message(response)
            self.close_ws()

        else:
            response["response"] = {
                "result":"ok",
                "code":"200"
            }
            self.write_message(response)

    def add_msg(self, msg):
        logging.debug("add_msg _userid = %s, msg = %r" % (self._userid, msg))
        notify = {}
        notify[self.KEY_TYPE] = self.MSGTYPE_NOTIFY_REQ
        event = json.loads(msg.decode("utf-8"))
        notify["event"] = event
        notify_id = event.get("id", "")
        if not notify_id:
            return

        self._messages[notify_id] = notify
        
        try:
            self.write_message(notify)
        except Exception as e:
            ogging.error("add_msg push exception {0}".format(e))
            self.close_ws()
        
    def add_tmpmsg(self, msg):
        notify = {}
        notify[self.KEY_TYPE] = self.MSGTYPE_NOTIFY_REQ
        event = json.loads(msg.decode("utf-8"))
        notify["event"] = event
        try:
            self.write_message(notify)
        except Exception as e:
            logging.error("add_tmpmsg exception {0}".format(e))
            self.close_ws()

    def push_msg(self):
        #push one message to client
        for key, value in self._messages.items():
            logging.debug("push_msg _userid = %s, msg = %r" % (self._userid, value))
            if value:
                try:
                    timeout = value.get("timeout", 0) + 1
                    if timeout > 3:
                        self.write_message(value)
                    else:
                         value["timeout"] = timeout
                except Exception as e:
                    logging.error("push_msg push exception {0}".format(e))
                    self.close_ws()
                    break

    def save_message(self):
        logging.debug("save messages %s" % self._userid)
        #republish the message to rabbitmq, client will receive message when itconnect again
        for key, value in self._messages.items():
            notify = value.get("event", {})    
            if notify.get("rs_expire", None):
                return
            mickey.publish.publish_one(self._userid, notify)
              
    def close_ws(self):
        logging.info("close websocket %s tmpkey = %s" % (self._userid, self._tmpkey))
        self.clean()
        self.close()   
        
    def clean(self):
        logging.info("clean websocket %s tmpkey = %s" % (self._userid, self._tmpkey))
        self._closed = True
        #disconnect pika when client close websocket
        if self._pikaclient:
            self._pikaclient.close()
            self._pikaclient = None
            logging.info("clean websocket _pikaclient cleaned %s" % self._userid)
            self.save_message()        
        else:
            logging.info("_pikaclient is already closed %s" % self._userid)                       

    def savekey(self):
        if self._iostoken:
            token_key = REDIS_IOS_PREFIX + self._userid
            mickey.redis.write_to_redis(REDIS_IOS_PREFIX + self._userid, self._iostoken)
            mickey.redis.write_to_redis(self._iostoken, self._userid)

    def setuppubsub(self):
        #user = yield self._coll.find_one({"id":self._userid})
        #contacts = []
        #if user:
            #contacts = user.get("contacts", [])

        if self._closed:
            logging.info("websocket is already closed %s" % self._userid)
            return

        format_plat = ""
        #sub notify of specific platform
        if self._platform == "pc":
            format_plat = self._platform
            #pc only receive notify when online
            self._tmp_routings.append(self._userid)
            #some message only send to pc
            self._tmp_routings.append(self._platform + self._userid)
            #self._tmp_routings.extend(["pub" + x.get("id", "") for x in contacts])
        else:
            format_plat = "mob"
            self._queuename = self._userid
            self._routings.append(self._userid)
            #self._routings.extend(["pub" + x.get("id", "") for x in contacts])

        """
        gen temp routings to receive unreliable messages
        some notification is useful just at the time when it happened
        """
        time_random = datetime.utcnow().strftime('_%H%M%S%f')[:-3]
        self._tmpkey = time_random

        self._tmp_queuename = "temp%s%s%s" % (self._userid, time_random, format_plat)
        self._tmp_routings.append("temp%s" % self._userid)

        self._pikaclient = PikaConsumer(self._userid,
                                        self,
                                        exchange = 'user',
                                        exchange_type = 'direct',
                                        queuename = self._queuename,
                                        routings = self._routings,
                                        tmp_queuename = self._tmp_queuename,
                                        tmp_routings = self._tmp_routings)

        tornado.ioloop.IOLoop.instance().add_timeout(1000, self.startpika)

    def startpika(self):
        if self._closed:
            logging.info("websocket is already closed %s" % self._userid)
            return

        if self._pikaclient:
            self._pikaclient.start()

    @tornado.gen.coroutine
    def handle_identify(self, data):
        # one connection only accept identify
        if self._authed:
            logging.error("already start authed, receive auth message again")
            return
        self._authed = True
        auth_code = 200

        #read information of identify request
        token = data.get(self.KEY_TOKEN, "")
        self._platform = data.get(self.KEY_PLAT, "uns").lower()
        self._iostoken = data.get(self.KEY_IOS, "")

        #auth the client
        if token:
            self._token = token
            #get the userid by the token
            (self._userid, auth_code) = yield mickey.userfetcher.getuser(token, provison = True)
            if self._closed:
                logging.error("websoket already closed %s" % token)
                return

            if not self._userid:
                logging.error("get user information failed, the token is invalid %s, connection will be closed" % token)
                self.sendauthresponse(auth_code)
                return
            
            #save the device key of client, device push only iphone was supported
            self.savekey()
        
            #create the subscribe client to receive notification for user
            logging.debug("start create sub client for %s" % self._userid)
            self.setuppubsub()
        
        if self._closed:
            logging.error("websoket already closed %s" % token)
            return
        
        self.sendauthresponse(auth_code) 
        #after identify we can handle other messages
        self._handlers[self.MSGTYPE_NOTIFY_RES] = self.handle_notifyres
        self._handlers[self.MSGTYPE_HEARBEAT_REQ] = self.handle_heartbeat
        self._handlers[self.MSGTYPE_PUB_REQ] = self.handle_pub
            
    def handle_notifyres(self, data):
        #receive notify response from client push left messages to it
        logging.debug("notify response received %s" % self._userid)
        noti_id = data.get("id", "")
        #remove the pushed message
        self._messages.pop(noti_id, None) 

    def handle_heartbeat(self, data):
        logging.info("heartbeat received %s tmpkey = %s" % (self._userid, self._tmpkey))
        if self._pikaclient:
            logging.info("websocekt %s is normal" % self._userid)
            self._timeout = 0
            response = {}
            response[self.KEY_TYPE] = self.MSGTYPE_HEARBEAT_RES
            self.write_message(response)
            #push the message to client
            self.push_msg()
       
    def handle_pub(self, data):
        logging.info("pub of %s received tmpkey = %s" % (self._userid, self._tmpkey))
        event = data.get("event", {})
        eventid = data.get("id", None)
        if not event or not eventid:
            logging.error("invalid event received")
            return

        receivers = event.get("receivers", [])
        notify = event.get("notify", {})

        if not receivers or not notify:
            logging.error("event without receivers or notify")
            self.send_pubres(eventid, "403", "event without receivers or notify")
            return

        noty_id = notify.get('id', None)
        pub_type = notify.get('pub_type', None)
        nty_type = notify.get('nty_type', None)

        if noty_id:
            logging.error("invalid notify with id")
            self.send_pubres(eventid, "403", "notify used reserve key word 'id'")
            return

        if not pub_type or not nty_type:
            logging.error("invalid notify without pub_type or nty_type")
            self.send_pubres(eventid, "403", "notify without pub_type or nty_type")
            return

        if pub_type != 'online' and pub_type != 'any':
            logging.error("invalid notify invalid pub_type")
            self.send_pubres(eventid, "403", "notify with invalid pub_type")
            return

        if nty_type != 'app' and nty_type != 'device':
            logging.error("invalid notify invalid nty_type")
            self.send_pubres(eventid, "403", "notify with invalid nty_type")
            return

        nt_headers = {}
        nt_headers["from"] = self._userid
        notify["headers"] = nt_headers
        mickey.publish.publish_multi(receivers, notify)
        self.send_pubres(eventid, "0", "")

    @tornado.gen.coroutine
    def handle_http(self, data):
        req_id = data.get("id", "")
        request = data.get("request", {})
        if not req_id or not request:
            logging.error("invalid request")
            return

        url = request.get("url", "")
        method = request.get("method", "")
        headers = request.get("headers", None)
        body = request.get("body", None)
        http_response = yield MickeyWsHttpGate.httprequest(req_id, url, method, headers, body)
        self.write_message(http_response)

    def handle_unexpect(self, data):
        #invalid message received
        logging.error("invalid message received %s %r" % (self._userid, data))
        self.close_ws()     
                
    def send_pubres(self, eid, code, desc):
        res = {}
        res[self.KEY_TYPE] = self.MSGTYPE_PUB_RES
        res["id"] = eid
        res["code"] = code
        res["desc"] = desc
        self.write_message(res)

    @tornado.gen.coroutine
    def on_message(self, msg):
        try:
            json_data = json.loads(msg)
            msg_type = json_data.get(self.KEY_TYPE, "")
            if msg_type == self.MSGTYPE_HTTP_REQ:
                yield self.handle_http(json_data)
                return
            elif msg_type == self.MSGTYPE_IDENTIFY_REQ:
                yield self.handle_identify(json_data)
                return
            else:
                self._handlers.get(msg_type, self.handle_unexpect)(json_data)
        except Exception as e:
            logging.error("exception message received {0}".format(e))
            self.close_ws()
            pass

    def on_close(self):
        logging.debug("disconnected %s tmpkey = %s" % (self._userid, self._tmpkey))
        self.clean()
Example #4
0
class ConnectHandler(tornado.websocket.WebSocketHandler):

    def initialize(self):
        self.application.keyid = self.application.keyid + 1
        self.key = self.application.flag + str(self.application.keyid)
        self.userid = None
        self.pika_client = None
        self.pika_prepare = False
        self.bind = False

    def check_origin(self, origin):
        return True

    def open(self):
        tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 300, self.authcheck)

    def authcheck(self):
        if not self.userid:
            logging.error("auth timeout close socket %s" % self.key)
            self.close()
        else:
            logging.debug("bind")

    def bindponse(self):
        logging.debug("bind response")
        
        response = {}
        response["type"] = "bind_res"

        bindinfo = {}
        bindinfo["plat"] = "pc"
        bindinfo["action"] = "login"
        bindinfo["qrkey"] = self.key

        response["response"] = {
            "result":"ok",
            "code":"200",
            "bindkey": bindinfo
        }
        
        try:
            self.write_message(response)
        except Exception as e:
            logging.error("websocket exception {0}".format(e))
            self.close()

    def add_msg(self, msg):
        logging.debug("add_msg key = %s, msg = %r" % (self.key, msg))
        notify = json.loads(msg.decode("utf-8"))

        msg_type = notify.get("type", "")
        if msg_type == "user_notify":
            content = notify.get("notify", {})
            if content:
                self.userid = content.get("userid", "")

        try:
            self.write_message(notify)
        except Exception as e:
            logging.error("websocket exception {0}".format(e))
            self.close()

    def add_tmpmsg(self, msg):
        logging.debug("add_msg key = %s, msg = %r" % (self.key, msg))
        notify = json.loads(msg.decode("utf-8"))

        msg_type = notify.get("type", "")
        if msg_type == "user_notify":
            content = notify.get("notify", {})
            if content:
                self.userid = content.get("userid", "")

        try:
            self.write_message(notify)
        except Exception as e:
            logging.error("websocket exception {0}".format(e))
            self.close()


    def listen(self):
        logging.info("begin to create pika client")
        routings = []
        routings.append(self.key)
        self.pika_client = PikaConsumer(self.key, 
                                        self, 
                                        exchange = "pclogin",
                                        exchange_type = "direct",
                                        tmp_queuename = self.key,
                                        tmp_routings = routings)
        self.pika_client.start()

    def connected(self):
        self.pika_prepare = True

        if not self.bind:
            self.bind = True
            self.bindponse()
        else:
            #send notify to mobile user, the pc version want to login
            logging.debug("already bind, send login request to the mobile user")
            self.sendloginreq()

    def sendloginreq(self):
        logging.debug("send login request to user")
        notify = {}
        notify["name"] = "mx.login.login_req"
        notify["pub_type"] = "any"
        notify["nty_type"] = "app"
        notify["rs_expire"] = 15000
        notify["loginkey"] = self.key
        self.application.userpublish.publish_one(self.userid, notify)

    def on_message(self, msg):
        try:
            logging.debug("on_message received %r" % msg)
            json_data = json.loads(msg)
            msg_type = json_data.get("type", "")
            
            if "bind_req" == msg_type:
                #check is already bind
                if self.bind:
                    logging.error("repeat bind")
                    self.close()
                    return

                #create pikaclient receive the response of mobile phone
                self.listen()

            elif "login_req" == msg_type:
                logging.debug("begin to handle login req")
                #client already bind to one user
                self.bind = True

                userid = json_data.get("userid", "")
                if userid:
                    self.userid = userid
                    if not self.pika_prepare:
                        logging.debug("begin to listen")
                        self.listen()
                    else:
                        logging.debug("already listen, send login request to the mobile user")
                        self.sendloginreq()
                        
                else:
                    logging.error("no user id")
                    self.close()
                    return
                    
            else:
                #invalid message received
                logging.error("SubHandler invalid message received %s" % self.userid)
                self.close()
                return
        
        except Exception as e:
            logging.error("ConnectHandler exception message received {0}".format(e))
            self.close()
            pass

    def on_close(self):
        #disconnect pika when client close websocket
        if self.pika_client:
            self.pika_client.close()