From 76ea4d25a4c6cd5d63c51de9d3b87816166fd322 Mon Sep 17 00:00:00 2001 From: Arksine Date: Thu, 3 Sep 2020 07:21:01 -0400 Subject: [PATCH] app: refactor websocket handler registration Websocket APIs are now generated using traditional namespaces, for example "printer.gcode.script" rather than "post_printer_gcode_script". Local endpoints that register multiple requests methods will have the method prefixed (ie:. "server.files.get_directory", "server.files.post_directory", "server.files.delete_directory") Signed-off-by: Eric Callahan --- moonraker/app.py | 59 ++++++++++++++++++++++++++++------------- moonraker/websockets.py | 25 +++++++++-------- 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/moonraker/app.py b/moonraker/app.py index 952cf02..e900d03 100644 --- a/moonraker/app.py +++ b/moonraker/app.py @@ -82,11 +82,11 @@ class MutableRouter(tornado.web.ReversibleRuleRouter): logging.exception(f"Unable to remove rule: {pattern}") class APIDefinition: - def __init__(self, endpoint, http_uri, ws_method, + def __init__(self, endpoint, http_uri, ws_methods, request_methods, parser): self.endpoint = endpoint self.uri = http_uri - self.ws_method = ws_method + self.ws_methods = ws_methods if not isinstance(request_methods, list): request_methods = [request_methods] self.request_methods = request_methods @@ -146,15 +146,15 @@ class MoonrakerApp: def register_remote_handler(self, endpoint): if endpoint in RESERVED_ENDPOINTS: return - api_def = self.api_cache.get( - endpoint, self._create_api_definition(endpoint)) + api_def = self._create_api_definition(endpoint) if api_def.uri in self.registered_base_handlers: # reserved handler or already registered return logging.info( - f"Registering remote endpoint: " - f"({' '.join(api_def.request_methods)}) {api_def.uri}") - self.wsm.register_handler(api_def) + f"Registering remote endpoint - " + f"HTTP: ({' '.join(api_def.request_methods)}) {api_def.uri}; " + f"Websocket: {', '.join(api_def.ws_methods)}") + self.wsm.register_remote_handler(api_def) params = {} params['server'] = self.server params['auth'] = self.auth @@ -164,17 +164,18 @@ class MoonrakerApp: api_def.uri, RemoteRequestHandler, params) self.registered_base_handlers.append(api_def.uri) - def register_local_handler(self, uri, ws_method, request_methods, + def register_local_handler(self, uri, request_methods, callback, http_only=False): if uri in self.registered_base_handlers: return api_def = self._create_api_definition( - uri, ws_method, request_methods) - logging.info( - f"Registering local endpoint: " - f"({' '.join(request_methods)}) {uri}") + uri, request_methods, is_remote=False) + msg = "Registering local endpoint - " + msg += f"HTTP: ({' '.join(request_methods)}) {uri}" if not http_only: - self.wsm.register_handler(api_def, callback) + msg += f"; Websocket: {', '.join(api_def.ws_methods)}" + self.wsm.register_local_handler(api_def, callback) + logging.info(msg) params = {} params['server'] = self.server params['auth'] = self.auth @@ -216,21 +217,41 @@ class MoonrakerApp: self.wsm.remove_handler(api_def.uri) self.mutable_router.remove_handler(api_def.ws_method) - def _create_api_definition(self, endpoint, ws_method=None, - request_methods=['GET', 'POST']): + def _create_api_definition(self, endpoint, request_methods=[], + is_remote=True): if endpoint in self.api_cache: return self.api_cache[endpoint] if endpoint[0] == '/': uri = endpoint - else: + elif is_remote: uri = "/printer/" + endpoint - if ws_method is None: - ws_method = uri[1:].replace('/', '_') + else: + uri = "/server/" + endpoint + ws_methods = [] + if is_remote: + # Remote requests accept both GET and POST requests. These + # requests execute the same callback, thus they resolve to + # only a single websocket method. + ws_methods.append(uri[1:].replace('/', '.')) + request_methods = ['GET', 'POST'] + else: + name_parts = uri[1:].split('/') + if len(request_methods) > 1: + for req_mthd in request_methods: + func_name = req_mthd.lower() + "_" + name_parts[-1] + ws_methods.append(".".join(name_parts[:-1] + [func_name])) + else: + ws_methods.append(".".join(name_parts)) + if not is_remote and len(request_methods) != len(ws_methods): + raise self.server.error( + "Invalid API definition. Number of websocket methods must " + "match the number of request methods") if endpoint.startswith("objects/"): parser = _status_parser else: parser = _default_parser - api_def = APIDefinition(endpoint, uri, ws_method, + + api_def = APIDefinition(endpoint, uri, ws_methods, request_methods, parser) self.api_cache[endpoint] = api_def return api_def diff --git a/moonraker/websockets.py b/moonraker/websockets.py index f551bd3..b1b7576 100644 --- a/moonraker/websockets.py +++ b/moonraker/websockets.py @@ -133,21 +133,20 @@ class WebsocketManager: async def _handle_metadata_update(self, metadata): await self.notify_websockets("metadata_update", metadata) - def register_handler(self, api_def, callback=None): - for r_method in api_def.request_methods: - cmd = r_method.lower() + '_' + api_def.ws_method - if callback is not None: - # Callback is a local method - rpc_cb = self._generate_local_callback( - api_def.endpoint, r_method, callback) - else: - # Callback is a remote method - rpc_cb = self._generate_callback(api_def.endpoint) - self.rpc.register_method(cmd, rpc_cb) + def register_local_handler(self, api_def, callback): + for ws_method, req_method in \ + zip(api_def.ws_methods, api_def.request_methods): + rpc_cb = self._generate_local_callback( + api_def.endpoint, req_method, callback) + self.rpc.register_method(ws_method, rpc_cb) + + def register_remote_handler(self, api_def): + ws_method = api_def.ws_methods[0] + rpc_cb = self._generate_callback(api_def.endpoint) + self.rpc.register_method(ws_method, rpc_cb) def remove_handler(self, ws_method): - for prefix in ["get", "post", "delete"]: - self.rpc.remove_method(prefix + "_" + ws_method) + self.rpc.remove_method(ws_method) def _generate_callback(self, endpoint): async def func(**kwargs):