#!/usr/bin/python3 # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 import os import json import uuid from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop from tornado.web import StaticFileHandler, Application, RequestHandler from tornado.websocket import WebSocketHandler import logging logger = logging.getLogger(__name__) DEBUG_HTTP = False BASE_PATH = os.path.normpath(os.path.abspath(os.path.dirname(__file__))) STATIC_PATH = os.path.join(BASE_PATH, 'static') VERSION = 1 config = {} users = set() rooms = {} class MainHandler(RequestHandler): def initialize(self): pass def get(self, path): path = os.path.join(STATIC_PATH, 'index.html') with open(path) as fd: content = fd.read() content = content.replace('.js?0', '.js?%s' % VERSION) content = content.replace('config = {}', 'config = %s'%json.dumps(config)) self.set_header('Content-Type', 'text/html') self.set_header('Content-Length', str(len(content))) self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate') self.set_header('Pragma', 'no-cache') self.set_header('Expires', '0') self.write(content) class SocketHandler(WebSocketHandler): def open(self): id = None while id is None or id in users: id = str(uuid.uuid1()).split('-')[0] users.add(id) self.id = id def on_message(self, message): try: data = json.loads(message) event = data[0] room = data[1] except: logger.error('failed', message) return if event == 'join': if not room in rooms: rooms[room] = [] if not self in rooms[room]: rooms[room].append(self) for ws in rooms[room]: ws.post(['join', self.id]) elif event == 'leave': if self in rooms.get(room, []): rooms[room].remove(self) for ws in rooms[room]: ws.post(['leave', self.id]) elif event == 'update': if self in rooms[room]: data[1] = self.id for ws in rooms[room]: if ws != self: ws.post(data) def on_close(self): for room in rooms: if self in rooms[room]: rooms[room].remove(self) for ws in rooms[room]: ws.post(['leave', self.id]) users.remove(self.id) def post(self, data): try: message = json.dumps(data) main = IOLoop.instance() if self.ws_connection is None: self.on_close() else: main.add_callback(lambda: self.write_message(message)) except: logger.debug('failed to send %s %s', self, data, exc_info=True) def log_request(handler): if DEBUG_HTTP: if handler.get_status() < 400: log_method = logger.info elif handler.get_status() < 500: log_method = logger.warning else: log_method = logger.error request_time = 1000.0 * handler.request.request_time() log_method("%d %s %.2fms", handler.get_status(), handler._request_summary(), request_time) if __name__ == '__main__': if DEBUG_HTTP: log_format='%(asctime)s:%(levelname)s:%(name)s:%(message)s' logging.basicConfig(level=logging.DEBUG, format=log_format) options = { 'debug': False, 'log_function': log_request, 'gzip': True } handlers = [ (r'/(favicon.ico)', StaticFileHandler, { 'path': STATIC_PATH }), (r'/static/(.*)', StaticFileHandler, { 'path': STATIC_PATH }), (r'/ws', SocketHandler), (r"(.*)", MainHandler), ] with open(os.path.join(BASE_PATH, 'config.json')) as fd: config = json.load(fd) address = os.environ.get('ADDRESS', '') port = int(os.environ.get('PORT', 8081)) http_server = HTTPServer(Application(handlers, **options)) http_server.listen(port, address) main = IOLoop.instance() main.start()