2017-02-18 12:45:48 +13:00
|
|
|
from datetime import datetime
|
|
|
|
|
from tornado.escape import json_encode, json_decode
|
2017-03-07 18:30:51 +13:00
|
|
|
import tornado.ioloop, tornado.web, tornado.websocket, tornado.template
|
2019-12-30 21:13:15 +13:00
|
|
|
import random, string, logging, uuid, subprocess, pykka, ast, logging, json, urllib, requests, time, asyncio
|
2019-11-19 16:00:08 +13:00
|
|
|
|
2019-12-24 13:52:50 +13:00
|
|
|
from .mem import iris
|
2017-02-18 12:45:48 +13:00
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
class WebsocketHandler(tornado.websocket.WebSocketHandler):
|
2019-11-19 16:00:08 +13:00
|
|
|
|
2017-02-18 12:45:48 +13:00
|
|
|
# initiate (not the actual object __init__, but run shortly after)
|
|
|
|
|
def initialize(self, core, config):
|
2019-11-19 16:00:08 +13:00
|
|
|
self.core = core
|
|
|
|
|
self.config = config
|
2017-02-18 12:45:48 +13:00
|
|
|
|
|
|
|
|
def check_origin(self, origin):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def open(self):
|
|
|
|
|
|
2019-04-25 14:49:51 +12:00
|
|
|
# Get the client's IP. If it's local, then use it's proxy origin
|
2017-03-28 21:36:14 +13:00
|
|
|
ip = self.request.remote_ip
|
2017-04-07 19:40:33 +12:00
|
|
|
if (ip == '127.0.0.1' and hasattr(self.request.headers,'X-Forwarded-For')):
|
2017-03-28 21:36:14 +13:00
|
|
|
ip = self.request.headers['X-Forwarded-For']
|
|
|
|
|
|
2019-04-25 14:49:51 +12:00
|
|
|
# Construct our initial client object, and add to our list of connections
|
2017-02-18 12:45:48 +13:00
|
|
|
client = {
|
2019-12-24 13:52:50 +13:00
|
|
|
'connection_id': iris.generateGuid(),
|
2017-03-28 21:36:14 +13:00
|
|
|
'ip': ip,
|
2019-04-25 14:49:51 +12:00
|
|
|
'created': datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S')
|
2017-02-18 12:45:48 +13:00
|
|
|
}
|
|
|
|
|
|
2019-11-19 16:00:08 +13:00
|
|
|
self.connection_id = client['connection_id']
|
2019-04-25 14:49:51 +12:00
|
|
|
|
2019-12-24 13:52:50 +13:00
|
|
|
iris.add_connection(connection=self, client=client)
|
2019-11-19 16:00:08 +13:00
|
|
|
|
2017-02-18 12:45:48 +13:00
|
|
|
|
2019-12-30 08:49:24 +13:00
|
|
|
async def on_message(self, message):
|
2018-03-19 16:09:27 +13:00
|
|
|
logger.debug("Iris websocket message received: "+message)
|
2019-11-19 16:00:08 +13:00
|
|
|
|
2017-02-18 12:45:48 +13:00
|
|
|
message = json_decode(message)
|
|
|
|
|
|
2018-03-12 21:39:27 +13:00
|
|
|
if 'id' in message:
|
|
|
|
|
id = message['id']
|
2017-02-18 12:45:48 +13:00
|
|
|
else:
|
2018-03-12 21:39:27 +13:00
|
|
|
id = None
|
|
|
|
|
|
|
|
|
|
if 'jsonrpc' not in message:
|
2018-03-19 16:09:27 +13:00
|
|
|
self.handle_result(id=id, error={'id': id, 'code': 32602, 'message': 'Invalid JSON-RPC request (missing property "jsonrpc")'})
|
2019-11-19 16:00:08 +13:00
|
|
|
|
2018-03-13 08:57:40 +13:00
|
|
|
if 'params' in message:
|
|
|
|
|
params = message['params']
|
2017-02-18 12:45:48 +13:00
|
|
|
|
2018-03-13 08:57:40 +13:00
|
|
|
# Handle hard-coded connection_id in messages
|
|
|
|
|
# Otherwise include the origin connection of this message
|
|
|
|
|
if 'connection_id' not in params:
|
|
|
|
|
message['params']['connection_id'] = self.connection_id
|
|
|
|
|
else:
|
|
|
|
|
params = {}
|
2017-02-18 12:45:48 +13:00
|
|
|
|
|
|
|
|
# call the method, as specified in payload
|
|
|
|
|
if 'method' in message:
|
|
|
|
|
|
|
|
|
|
# make sure the method exists
|
2019-11-23 14:08:47 +13:00
|
|
|
if hasattr(iris, message['method']):
|
2019-08-12 07:30:23 +12:00
|
|
|
try:
|
2020-01-01 12:13:18 +13:00
|
|
|
|
|
|
|
|
# For async methods we need to await, but it must be ommited for syncronous methods
|
2019-12-30 21:13:15 +13:00
|
|
|
if asyncio.iscoroutinefunction(getattr(iris, message['method'])):
|
|
|
|
|
await getattr(iris, message['method'])(data=params, callback=lambda response, error=False: self.handle_result(id=id, method=message['method'], response=response, error=error))
|
|
|
|
|
else:
|
|
|
|
|
getattr(iris, message['method'])(data=params, callback=lambda response, error=False: self.handle_result(id=id, method=message['method'], response=response, error=error))
|
2019-11-19 16:00:08 +13:00
|
|
|
except Exception as e:
|
2019-08-12 07:30:23 +12:00
|
|
|
logger.error(str(e))
|
2017-02-18 12:45:48 +13:00
|
|
|
|
|
|
|
|
else:
|
2018-03-19 16:09:27 +13:00
|
|
|
self.handle_result(error={'id': id, 'code': 32601, 'message': 'Method "'+message['method']+'" does not exist'}, id=id)
|
2017-10-20 21:49:41 +13:00
|
|
|
return
|
2017-02-18 12:45:48 +13:00
|
|
|
else:
|
2018-03-19 16:09:27 +13:00
|
|
|
self.handle_result(error={'id': id, 'code': 32602, 'message': 'Method key missing from request'}, id=id)
|
2017-10-20 21:49:41 +13:00
|
|
|
return
|
2017-02-18 12:45:48 +13:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def on_close(self):
|
2019-12-24 13:52:50 +13:00
|
|
|
iris.remove_connection(connection_id=self.connection_id)
|
2017-02-18 12:45:48 +13:00
|
|
|
|
2017-10-13 10:02:06 +13:00
|
|
|
##
|
|
|
|
|
# Handle a response from our core
|
|
|
|
|
# This is just our callback from an Async request
|
|
|
|
|
##
|
2018-03-19 16:09:27 +13:00
|
|
|
def handle_result(self, *args, **kwargs):
|
2018-03-13 08:57:40 +13:00
|
|
|
id = kwargs.get('id', False)
|
|
|
|
|
method = kwargs.get('method', None)
|
2017-10-13 20:48:35 +13:00
|
|
|
response = kwargs.get('response', None)
|
2017-10-20 21:49:41 +13:00
|
|
|
error = kwargs.get('error', None)
|
2018-03-12 21:39:27 +13:00
|
|
|
request_response = {
|
|
|
|
|
'id': id,
|
2018-03-13 08:57:40 +13:00
|
|
|
'jsonrpc': '2.0',
|
|
|
|
|
'method': method
|
2018-03-12 21:39:27 +13:00
|
|
|
}
|
2017-10-13 20:48:35 +13:00
|
|
|
|
2017-10-20 21:49:41 +13:00
|
|
|
# We've been given an error
|
|
|
|
|
if error:
|
2018-03-12 21:39:27 +13:00
|
|
|
error['id'] = id
|
|
|
|
|
request_response['error'] = error
|
2017-10-20 21:49:41 +13:00
|
|
|
|
2017-10-13 21:19:37 +13:00
|
|
|
# We've been handed an AsyncHTTPClient callback. This is the case
|
|
|
|
|
# when our request calls subsequent external requests (eg Spotify, Genius)
|
2017-10-20 21:49:41 +13:00
|
|
|
elif isinstance(response, tornado.httpclient.HTTPResponse):
|
2018-03-12 21:39:27 +13:00
|
|
|
request_response['result'] = response.body
|
2017-10-13 21:19:37 +13:00
|
|
|
|
|
|
|
|
# Just a regular json object, so not an external request
|
2017-10-13 10:02:06 +13:00
|
|
|
else:
|
2018-03-12 21:39:27 +13:00
|
|
|
request_response['result'] = response
|
2019-11-19 16:00:08 +13:00
|
|
|
|
2017-10-20 21:49:41 +13:00
|
|
|
# Respond to the original request
|
2018-03-20 21:47:59 +13:00
|
|
|
data = request_response
|
|
|
|
|
data['recipient'] = self.connection_id
|
2019-12-24 13:52:50 +13:00
|
|
|
iris.send_message(data=data)
|
2017-10-13 10:02:06 +13:00
|
|
|
|
2017-02-18 12:45:48 +13:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-11-19 16:00:08 +13:00
|
|
|
|
2017-02-18 12:45:48 +13:00
|
|
|
class HttpHandler(tornado.web.RequestHandler):
|
|
|
|
|
|
|
|
|
|
def set_default_headers(self):
|
|
|
|
|
self.set_header("Access-Control-Allow-Origin", "*")
|
2019-11-19 16:00:08 +13:00
|
|
|
self.set_header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, Client-Security-Token, Accept-Encoding")
|
2017-02-18 12:45:48 +13:00
|
|
|
|
|
|
|
|
def initialize(self, core, config):
|
|
|
|
|
self.core = core
|
|
|
|
|
self.config = config
|
2017-10-06 09:07:28 +13:00
|
|
|
|
|
|
|
|
# Options request
|
|
|
|
|
# This is a preflight request for CORS requests
|
|
|
|
|
def options(self, slug=None):
|
|
|
|
|
self.set_status(204)
|
|
|
|
|
self.finish()
|
2019-11-19 16:00:08 +13:00
|
|
|
|
2019-12-30 08:49:24 +13:00
|
|
|
async def get(self, slug=None):
|
2017-02-18 12:45:48 +13:00
|
|
|
|
2018-03-19 16:09:27 +13:00
|
|
|
id = int(time.time())
|
2018-03-12 21:39:27 +13:00
|
|
|
|
2017-02-18 22:04:05 +13:00
|
|
|
# make sure the method exists
|
2019-11-23 14:08:47 +13:00
|
|
|
if hasattr(iris, slug):
|
2019-08-12 07:30:23 +12:00
|
|
|
try:
|
2020-01-22 16:04:19 +13:00
|
|
|
|
2020-01-01 12:13:18 +13:00
|
|
|
# For async methods we need to await, but it must be ommited for syncronous methods
|
2019-12-30 21:13:15 +13:00
|
|
|
if asyncio.iscoroutinefunction(getattr(iris, slug)):
|
|
|
|
|
await getattr(iris, slug)(request=self, callback=lambda response, error=False: self.handle_result(id=id, method=slug, response=response, error=error))
|
|
|
|
|
else:
|
|
|
|
|
getattr(iris, slug)(request=self, callback=lambda response, error=False: self.handle_result(id=id, method=slug, response=response, error=error))
|
2019-11-19 16:00:08 +13:00
|
|
|
except Exception as e:
|
2019-08-12 07:30:23 +12:00
|
|
|
logger.error(str(e))
|
2019-11-19 16:00:08 +13:00
|
|
|
|
2017-10-04 16:47:15 +13:00
|
|
|
else:
|
2018-04-17 15:58:39 +12:00
|
|
|
self.handle_result(id=id, error={'code': 32601, 'message': "Method "+slug+" does not exist"})
|
2017-10-20 21:49:41 +13:00
|
|
|
return
|
2017-10-13 10:02:06 +13:00
|
|
|
|
2019-12-30 08:49:24 +13:00
|
|
|
async def post(self, slug=None):
|
2017-10-04 16:47:15 +13:00
|
|
|
|
2018-03-19 16:09:27 +13:00
|
|
|
id = int(time.time())
|
2018-03-12 21:39:27 +13:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
params = json.loads(self.request.body.decode('utf-8'))
|
2019-11-19 16:00:08 +13:00
|
|
|
except:
|
2018-03-19 16:09:27 +13:00
|
|
|
self.handle_result(id=id, error={'code': 32700, 'message': "Missing or invalid payload"})
|
2018-03-12 21:39:27 +13:00
|
|
|
return
|
|
|
|
|
|
2017-10-04 16:47:15 +13:00
|
|
|
# make sure the method exists
|
2019-11-23 14:08:47 +13:00
|
|
|
if hasattr(iris, slug):
|
2017-10-04 16:47:15 +13:00
|
|
|
try:
|
2019-12-30 21:13:15 +13:00
|
|
|
if asyncio.iscoroutinefunction(getattr(iris, slug)):
|
|
|
|
|
await getattr(iris, slug)(data=params, request=self.request, callback=lambda response=False, error=False: self.handle_result(id=id, method=slug, response=response, error=error))
|
|
|
|
|
else:
|
|
|
|
|
getattr(iris, slug)(data=params, request=self.request, callback=lambda response=False, error=False: self.handle_result(id=id, method=slug, response=response, error=error))
|
2017-10-04 16:47:15 +13:00
|
|
|
|
2020-01-19 21:21:11 +13:00
|
|
|
except tornado.web.HTTPError as e:
|
2018-03-19 16:09:27 +13:00
|
|
|
self.handle_result(id=id, error={'code': 32601, 'message': "Invalid JSON payload"})
|
2017-10-20 21:49:41 +13:00
|
|
|
return
|
2017-10-04 16:47:15 +13:00
|
|
|
|
2017-02-18 12:45:48 +13:00
|
|
|
else:
|
2018-03-19 16:09:27 +13:00
|
|
|
self.handle_result(id=id, error={'code': 32601, 'message': "Method "+slug+" does not exist"})
|
2017-10-20 21:49:41 +13:00
|
|
|
return
|
2017-10-13 10:02:06 +13:00
|
|
|
|
|
|
|
|
##
|
|
|
|
|
# Handle a response from our core
|
|
|
|
|
# This is just our callback from an Async request
|
|
|
|
|
##
|
2018-03-19 16:09:27 +13:00
|
|
|
def handle_result(self, *args, **kwargs):
|
2018-03-12 21:39:27 +13:00
|
|
|
id = kwargs.get('id', None)
|
2018-03-13 08:57:40 +13:00
|
|
|
method = kwargs.get('method', None)
|
2017-10-20 21:49:41 +13:00
|
|
|
response = kwargs.get('response', None)
|
|
|
|
|
error = kwargs.get('error', None)
|
2018-03-12 21:39:27 +13:00
|
|
|
request_response = {
|
|
|
|
|
'id': id,
|
2018-03-13 08:57:40 +13:00
|
|
|
'jsonrpc': '2.0',
|
|
|
|
|
'method': method
|
2018-03-12 21:39:27 +13:00
|
|
|
}
|
2017-10-20 21:49:41 +13:00
|
|
|
|
|
|
|
|
if error:
|
2018-03-12 21:39:27 +13:00
|
|
|
request_response['error'] = error
|
2018-03-19 16:09:27 +13:00
|
|
|
self.set_status(400)
|
2017-10-20 21:49:41 +13:00
|
|
|
|
2017-10-13 21:19:37 +13:00
|
|
|
|
|
|
|
|
# We've been handed an AsyncHTTPClient callback. This is the case
|
|
|
|
|
# when our request calls subsequent external requests (eg Spotify, Genius).
|
|
|
|
|
# We don't need to wrap non-HTTPResponse responses as these are dicts
|
2020-01-22 16:04:19 +13:00
|
|
|
elif isinstance(response, tornado.httpclient.HTTPResponse):
|
2017-10-17 18:54:57 +13:00
|
|
|
|
2017-10-20 21:49:41 +13:00
|
|
|
# Digest JSON responses into JSON
|
2017-10-17 18:54:57 +13:00
|
|
|
content_type = response.headers.get('Content-Type')
|
|
|
|
|
if content_type.startswith('application/json') or content_type.startswith('text/json'):
|
2020-01-22 16:04:19 +13:00
|
|
|
body = json.loads(response.body.decode("utf-8") )
|
2017-10-20 21:49:41 +13:00
|
|
|
|
|
|
|
|
# Non-JSON so just copy as-is
|
2017-10-17 18:54:57 +13:00
|
|
|
else:
|
2020-01-22 16:04:19 +13:00
|
|
|
body = json_encode(response.body.decode("utf-8") )
|
2017-10-17 18:54:57 +13:00
|
|
|
|
2018-03-12 21:39:27 +13:00
|
|
|
request_response['result'] = body
|
2017-10-13 21:19:37 +13:00
|
|
|
|
2017-10-20 21:49:41 +13:00
|
|
|
# Regular ol successful response
|
|
|
|
|
else:
|
2018-03-12 21:39:27 +13:00
|
|
|
request_response['result'] = response
|
2017-10-20 21:49:41 +13:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# Write our response
|
2018-03-19 16:09:27 +13:00
|
|
|
|
2018-03-12 21:39:27 +13:00
|
|
|
self.write(request_response)
|
2017-10-13 10:02:06 +13:00
|
|
|
self.finish()
|
|
|
|
|
|
|
|
|
|
|
2020-01-07 23:28:34 +00:00
|
|
|
##
|
|
|
|
|
# Customised handler for react router URLS
|
|
|
|
|
#
|
|
|
|
|
# This routes all URLs to the same path, so that React can handle the path etc
|
|
|
|
|
##
|
|
|
|
|
class ReactRouterHandler(tornado.web.StaticFileHandler):
|
|
|
|
|
def initialize(self, path):
|
|
|
|
|
self.path = path
|
|
|
|
|
self.absolute_path = path
|
|
|
|
|
self.dirname = path.parent
|
|
|
|
|
self.filename = path.name
|
|
|
|
|
super().initialize(self.dirname)
|
|
|
|
|
|
|
|
|
|
def get(self, path=None, include_body=True):
|
|
|
|
|
return super().get(self.path, include_body)
|
2019-11-19 16:00:08 +13:00
|
|
|
|