# -*- coding: utf-8 -*-
"""
tornado websocket proxy for WF worker daemons
"""
# Copyright (C) 2015 ZetaOps Inc.
#
# This file is licensed under the GNU General Public License v3
# (GPLv3). See LICENSE.txt for details.
import json
import os, sys
import traceback
from uuid import uuid4
from tornado import websocket, web, ioloop
from tornado.escape import json_decode, json_encode
from tornado.httpclient import HTTPError
sys.path.insert(0, os.path.realpath(os.path.dirname(__file__)))
from ws_to_queue import QueueManager, log, settings
COOKIE_NAME = 'zopsess'
DEBUG = os.getenv("DEBUG", False)
# blocking_connection = BlockingConnectionForHTTP()
[docs]class SocketHandler(websocket.WebSocketHandler):
"""
websocket handler
"""
[docs] def check_origin(self, origin):
"""
Prevents CORS attacks.
Args:
origin: HTTP "Origin" header. URL of initiator of the request.
Returns:
True if origin is legit, otherwise False
"""
# FIXME: implement CORS checking
return True
def _get_sess_id(self):
# return self.sess_id;
sess_id = self.get_cookie(COOKIE_NAME)
return sess_id
[docs] def open(self):
"""
Called on new websocket connection.
"""
sess_id = self._get_sess_id()
if sess_id:
self.application.pc.websockets[self._get_sess_id()] = self
self.write_message(json.dumps({"cmd": "status", "status": "open"}))
else:
self.write_message(json.dumps({"cmd": "error", "error": "Please login", "code": 401}))
[docs] def on_message(self, message):
"""
called on new websocket message,
"""
log.debug("WS MSG for %s: %s" % (self._get_sess_id(), message))
self.application.pc.redirect_incoming_message(self._get_sess_id(), message, self.request)
[docs] def on_close(self):
"""
remove connection from pool on connection close.
"""
self.application.pc.unregister_websocket(self._get_sess_id())
# noinspection PyAbstractClass
[docs]class HttpHandler(web.RequestHandler):
"""
login handler class
"""
def _handle_headers(self):
"""
Do response processing
"""
origin = self.request.headers.get('Origin')
if not settings.DEBUG:
if origin in settings.ALLOWED_ORIGINS or not origin:
self.set_header('Access-Control-Allow-Origin', origin)
else:
log.debug("CORS ERROR: %s not allowed, allowed hosts: %s" % (origin,
settings.ALLOWED_ORIGINS))
raise HTTPError(403, "Origin not in ALLOWED_ORIGINS: %s" % origin)
else:
self.set_header('Access-Control-Allow-Origin', origin or '*')
self.set_header('Access-Control-Allow-Credentials', "true")
self.set_header('Access-Control-Allow-Headers', 'Content-Type')
self.set_header('Access-Control-Allow-Methods', 'OPTIONS')
self.set_header('Content-Type', 'application/json')
@web.asynchronous
[docs] def get(self, view_name):
"""
only used to display login form
Args:
view_name: should be "login"
"""
self.post(view_name)
@web.asynchronous
[docs] def post(self, view_name):
"""
login handler
"""
sess_id = None
input_data = {}
# try:
self._handle_headers()
# handle input
input_data = json_decode(self.request.body) if self.request.body else {}
input_data['path'] = view_name
# set or get session cookie
if not self.get_cookie(COOKIE_NAME) or 'username' in input_data:
sess_id = uuid4().hex
self.set_cookie(COOKIE_NAME, sess_id) # , domain='127.0.0.1'
else:
sess_id = self.get_cookie(COOKIE_NAME)
# h_sess_id = "HTTP_%s" % sess_id
input_data = {'data': input_data,
'_zops_remote_ip': self.request.remote_ip}
log.info("New Request for %s: %s" % (sess_id, input_data))
self.application.pc.register_websocket(sess_id, self)
self.application.pc.redirect_incoming_message(sess_id,
json_encode(input_data),
self.request)
[docs] def write_message(self, output):
log.debug("WRITE MESSAGE To CLIENT: %s" % output)
# if 'login_process' not in output:
# # workaround for premature logout bug (empty login form).
# # FIXME: find a better way to handle HTTP and SOCKET connections for same sess_id.
# return
self.write(output)
self.finish()
self.flush()
URL_CONFS = [
(r'/ws', SocketHandler),
(r'/(\w+)', HttpHandler),
]
app = web.Application(URL_CONFS, debug=DEBUG, autoreload=False)
[docs]def runserver(host=None, port=None):
"""
Run Tornado server
"""
host = host or os.getenv('HTTP_HOST', '0.0.0.0')
port = port or os.getenv('HTTP_PORT', '9001')
zioloop = ioloop.IOLoop.instance()
# setup pika client:
pc = QueueManager(zioloop)
app.pc = pc
pc.connect()
app.listen(port, host)
zioloop.start()
if __name__ == '__main__':
runserver()