138 lines
4.2 KiB
Python
Executable file
138 lines
4.2 KiB
Python
Executable file
#!/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()
|