class ChatServiceLiteHandler(tornado.web.RequestHandler): def initialize(self, config, auth): self.config = config self.auth_provider = auth self.persistent = PsycopgPersistentService(config) self.loader = tornado.template.Loader(os.path.join(rootdir, 'templates')) self.ctx = zmq.Context() self.push_sock = self.ctx.socket(zmq.PUSH) self.push_sock.connect(self.config.get('push_endpoint', 'tcp://127.0.0.1:60001')) def generate_(self, name = None, color = None, count = None): if name is None: name = urllib.parse.unquote(self.get_cookie('lite_chat_username', urllib.parse.quote('名無し'))) if color is None: color = urllib.parse.unquote(self.get_cookie('lite_chat_color', urllib.parse.quote('black'))); if count is None: try: count = int(self.get_cookie('lite_chat_count', '20')); except: pass messages = self.persistent.fetch_latest(count, None) for i in range(len(messages)): messages[i]['d'] = datetime.datetime.fromtimestamp(messages[i]['d'] / 1000).strftime('%m/%d %H:%M:%S') items = URL_RE.split(messages[i]['t']) for x in range(len(items)): if x % 2 == 0: items[x] = (items[x], None) else: items[x] = (urllib.parse.unquote_plus(items[x]), items[x]) messages[i]['t'] = items self.finish(self.loader.load('lite.html').generate(messages = messages, name = name, color = color, count = str(count))) def get(self): self.generate_() def post(self): name = self.get_argument('name', '名無し') body = self.get_argument('body', None) color = self.get_argument('color', 'black') count = 20 try: count = int(self.get_argument('count', '20')) except: pass self.set_cookie('lite_chat_username', urllib.parse.quote(name)) self.set_cookie('lite_chat_color', urllib.parse.quote(color)) self.set_cookie('lite_chat_count', str(count)) if body is not None and len(body) > 0: dt = datetime.datetime.now(datetime.timezone.utc) msgid = self.persistent.store(self.current_user, dt, name, color, None, body) self.push_sock.send(b'chat\0' + json.dumps({ 'n': name, 'c': color, 't': body, 'i': msgid, 'd': int((dt - UNIXTIME_ORIGIN).total_seconds() * 1000), 'g': None }).encode('utf-8')) self.redirect(urllib.parse.urlparse(self.request.headers.get('Referer', self.request.path)).path) def get_current_user(self): return self.auth_provider.get_user_id(self.request)
class ChatServiceWebSocketHandler(tornado.websocket.WebSocketHandler): def initialize(self, config, auth): self.config = config self.auth_provider = auth self.persistent = PsycopgPersistentService(config) self.authenticated_user_id = None def open(self): print('[ws::open]', self.request.headers) self.ctx = zmq.Context() self.sub_sock = self.ctx.socket(zmq.SUB) self.sub_sock.connect(self.config.get('sub_endpoint', 'tcp://127.0.0.1:60000')) self.sub_sock.setsockopt(zmq.SUBSCRIBE, b'chat\0') self.strm = zmq.eventloop.zmqstream.ZMQStream(self.sub_sock) self.strm.on_recv(self.on_message_from_hub) self.push_sock = self.ctx.socket(zmq.PUSH) self.push_sock.connect(self.config.get('push_endpoint', 'tcp://127.0.0.1:60001')) def on_message(self, message): req = json.loads(message) if not self.authenticated_user_id: if req['m'] == 'auth': user_id = req['user-id'] if not WebSocketAuth.verify_token(user_id, self.request, req['token']): raise tornado.web.HTTPError(401) self.authenticated_user_id = user_id return if req['m'] == 'post': dt = datetime.datetime.now(datetime.timezone.utc) req['d'] = int((dt - UNIXTIME_ORIGIN).total_seconds() * 1000) icon_hash = None if 'g' in req and req['g']: icon_hash = binascii.a2b_hex(req['g'].encode('ascii')) print(req) msg_hash = self.compute_message_hash(req) # TODO: ハッシュ値を使った重複チェック uid = self.persistent.store(self.current_user, dt, req['n'], req['c'], icon_hash, req['t']) self.push_sock.send(b'chat\0' + json.dumps({ 'n': req['n'], 'c': req['c'], 't': req['t'], 'i': uid, 'd': req['d'], 'g': req['g'] }).encode('utf-8')) self.write_message(json.dumps({ 'e': req['e'], 'r': 'ok' })) elif req['m'] == 'latest': if 'i' not in req: req['i'] = -1 self.write_message(json.dumps({ 'e': req['e'], 'r': 'ok', 'm': self.persistent.fetch_latest(req['c'], req['i']) })) elif req['m'] == 'store-icon': icon_binary = binascii.a2b_base64(req['d'].encode('ascii')) icon_hash = hashlib.sha1(icon_binary).digest() icon_hash_hex = binascii.b2a_hex(icon_hash).decode('ascii') self.persistent.store_icon(self.current_user, icon_hash, icon_binary, req['t'], '') self.write_message(json.dumps({ 'e': req['e'], 'r': 'ok', 'h': icon_hash_hex })) elif req['m'] == 'ping': self.write_message(json.dumps({ 'e': req['e'], 'r': 'ok' })) else: print('unknown:', req) def on_message_from_hub(self, messages): if not self.authenticated_user_id: return for msg in messages: obj = json.loads(msg[5:].decode('utf-8')) self.write_message(json.dumps({ 'm': 'strm', 'd': obj })) def on_close(self): print('[ws::close]') self.strm.close() self.sub_sock.close() self.push_sock.close() def compute_message_hash(self, obj): txt = self.current_user + "\0" + obj['r'] + "\0" + obj['n'] + "\0" + obj['c'] + "\0" + obj['t']; sha1 = hashlib.sha1() sha1.update(txt.encode('utf-8')) return sha1.digest() def get_current_user(self): # TODO: ChromiumがWebSocketのBasic認証に対応したら指定されたauth_providerを利用する if not self.authenticated_user_id: raise tornado.web.HTTPError(401) return self.authenticated_user_id