diff --git a/static-data/official-plugins/rpc/longrpc.py b/static-data/official-plugins/rpc/longrpc.py new file mode 100644 index 00000000..25af0b87 --- /dev/null +++ b/static-data/official-plugins/rpc/longrpc.py @@ -0,0 +1,41 @@ +""" + +""" +import threading +import os +import time +import traceback +import collections +from typing import Union + +import ujson +import jsonrpc + +from logger import log as logging + + +rpc_results = collections.deque(maxlen=10000) + +def get_results(id) -> Union[str, None]: + final = None + for result in rpc_results: + if str(result['id']) == str(id): + final = result + break + else: + return None + rpc_results.remove(final) + return ujson.dumps(final) + + +def _exec_rpc(rpc_json_str): + json_resp = jsonrpc.JSONRPCResponseManager.handle(rpc_json_str, jsonrpc.dispatcher) + data = json_resp.data + rpc_results.append(data) + +def threaded_rpc(rpc_json_str): + threading.Thread( + target=_exec_rpc, + args=(rpc_json_str,), + daemon=True, + name="JSON RPC").start() diff --git a/static-data/official-plugins/rpc/main.py b/static-data/official-plugins/rpc/main.py index dd4d7710..c5bb79b6 100644 --- a/static-data/official-plugins/rpc/main.py +++ b/static-data/official-plugins/rpc/main.py @@ -52,18 +52,47 @@ from onionrplugins import plugin_apis from rpc import blocks, pluginrpcmethods from rpc.addmodule import add_module_to_api +import longrpc plugin_apis['rpc.add_module_to_api'] = add_module_to_api +def _detect_cors_and_add_headers(): + cherrypy.response.headers['Access-Control-Allow-Headers'] = 'Content-Type' + cherrypy.response.headers['Access-Control-Allow-Origin'] = '*' + cherrypy.response.headers['Access-Control-Allow-Methods'] = 'POST' + if cherrypy.request.method == 'OPTIONS': + return True + return False + class OnionrRPC(object): + @cherrypy.expose + def queue_rpc(self): + if _detect_cors_and_add_headers(): + return '' + + rpc_request_json: str = cherrypy.request.body.read().decode('utf-8') + longrpc.threaded_rpc(rpc_request_json) + return 'ok' + + @cherrypy.expose + def get_rpc_result(self, id=0): + if _detect_cors_and_add_headers(): + return '' + + results = longrpc.get_results(id) + if not results: + return '"no result"' + return results + @cherrypy.expose def rpc(self): + # Basic RPC, intended for small amounts of work + # Use /queue_rpc for large workloads like creating blocks + # and getting results with /get_rpc_result?id= # Dispatcher is dictionary {: callable} - if cherrypy.request.method == 'OPTIONS': - cherrypy.response.headers['Access-Control-Allow-Origin'] = '*' - cherrypy.response.headers['Access-Control-Allow-Methods'] = 'POST' + if _detect_cors_and_add_headers(): return '' data = cherrypy.request.body.read().decode('utf-8') @@ -89,7 +118,10 @@ def on_beforecmdparsing(api, data=None): def on_afterinit(api, data=None): def ping(): return "pong" + def always_fails(): + raise Exception("This always fails") dispatcher['ping'] = ping + dispatcher['always_fails'] = always_fails pluginrpcmethods.add_plugin_rpc_methods() diff --git a/static-data/official-plugins/rpc/rpc/blocks.py b/static-data/official-plugins/rpc/rpc/blocks.py index dfcd8435..fa128f58 100644 --- a/static-data/official-plugins/rpc/rpc/blocks.py +++ b/static-data/official-plugins/rpc/rpc/blocks.py @@ -19,6 +19,7 @@ def get_blocks(timestamp): @dispatcher.add_method def create_block( block_data: 'base64', block_type: str, ttl: int, metadata: dict): + # TODO use a module from an old version to use multiprocessing to avoid blocking GIL # Wrapper for onionrblocks.create_block (take base64 to be compatible with RPC) bl = onionrblocks.create_anonvdf_block( base64.b64decode(block_data), block_type, ttl, **metadata)