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 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)
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()
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()