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
This commit is contained in:
Arksine 2020-09-03 07:21:01 -04:00
parent fdec6277eb
commit 76ea4d25a4
2 changed files with 52 additions and 32 deletions

View File

@ -82,11 +82,11 @@ class MutableRouter(tornado.web.ReversibleRuleRouter):
logging.exception(f"Unable to remove rule: {pattern}") logging.exception(f"Unable to remove rule: {pattern}")
class APIDefinition: class APIDefinition:
def __init__(self, endpoint, http_uri, ws_method, def __init__(self, endpoint, http_uri, ws_methods,
request_methods, parser): request_methods, parser):
self.endpoint = endpoint self.endpoint = endpoint
self.uri = http_uri self.uri = http_uri
self.ws_method = ws_method self.ws_methods = ws_methods
if not isinstance(request_methods, list): if not isinstance(request_methods, list):
request_methods = [request_methods] request_methods = [request_methods]
self.request_methods = request_methods self.request_methods = request_methods
@ -146,15 +146,15 @@ class MoonrakerApp:
def register_remote_handler(self, endpoint): def register_remote_handler(self, endpoint):
if endpoint in RESERVED_ENDPOINTS: if endpoint in RESERVED_ENDPOINTS:
return return
api_def = self.api_cache.get( api_def = self._create_api_definition(endpoint)
endpoint, self._create_api_definition(endpoint))
if api_def.uri in self.registered_base_handlers: if api_def.uri in self.registered_base_handlers:
# reserved handler or already registered # reserved handler or already registered
return return
logging.info( logging.info(
f"Registering remote endpoint: " f"Registering remote endpoint - "
f"({' '.join(api_def.request_methods)}) {api_def.uri}") f"HTTP: ({' '.join(api_def.request_methods)}) {api_def.uri}; "
self.wsm.register_handler(api_def) f"Websocket: {', '.join(api_def.ws_methods)}")
self.wsm.register_remote_handler(api_def)
params = {} params = {}
params['server'] = self.server params['server'] = self.server
params['auth'] = self.auth params['auth'] = self.auth
@ -164,17 +164,18 @@ class MoonrakerApp:
api_def.uri, RemoteRequestHandler, params) api_def.uri, RemoteRequestHandler, params)
self.registered_base_handlers.append(api_def.uri) 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): callback, http_only=False):
if uri in self.registered_base_handlers: if uri in self.registered_base_handlers:
return return
api_def = self._create_api_definition( api_def = self._create_api_definition(
uri, ws_method, request_methods) uri, request_methods, is_remote=False)
logging.info( msg = "Registering local endpoint - "
f"Registering local endpoint: " msg += f"HTTP: ({' '.join(request_methods)}) {uri}"
f"({' '.join(request_methods)}) {uri}")
if not http_only: 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 = {}
params['server'] = self.server params['server'] = self.server
params['auth'] = self.auth params['auth'] = self.auth
@ -216,21 +217,41 @@ class MoonrakerApp:
self.wsm.remove_handler(api_def.uri) self.wsm.remove_handler(api_def.uri)
self.mutable_router.remove_handler(api_def.ws_method) self.mutable_router.remove_handler(api_def.ws_method)
def _create_api_definition(self, endpoint, ws_method=None, def _create_api_definition(self, endpoint, request_methods=[],
request_methods=['GET', 'POST']): is_remote=True):
if endpoint in self.api_cache: if endpoint in self.api_cache:
return self.api_cache[endpoint] return self.api_cache[endpoint]
if endpoint[0] == '/': if endpoint[0] == '/':
uri = endpoint uri = endpoint
else: elif is_remote:
uri = "/printer/" + endpoint uri = "/printer/" + endpoint
if ws_method is None: else:
ws_method = uri[1:].replace('/', '_') 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/"): if endpoint.startswith("objects/"):
parser = _status_parser parser = _status_parser
else: else:
parser = _default_parser parser = _default_parser
api_def = APIDefinition(endpoint, uri, ws_method,
api_def = APIDefinition(endpoint, uri, ws_methods,
request_methods, parser) request_methods, parser)
self.api_cache[endpoint] = api_def self.api_cache[endpoint] = api_def
return api_def return api_def

View File

@ -133,21 +133,20 @@ class WebsocketManager:
async def _handle_metadata_update(self, metadata): async def _handle_metadata_update(self, metadata):
await self.notify_websockets("metadata_update", metadata) await self.notify_websockets("metadata_update", metadata)
def register_handler(self, api_def, callback=None): def register_local_handler(self, api_def, callback):
for r_method in api_def.request_methods: for ws_method, req_method in \
cmd = r_method.lower() + '_' + api_def.ws_method zip(api_def.ws_methods, api_def.request_methods):
if callback is not None: rpc_cb = self._generate_local_callback(
# Callback is a local method api_def.endpoint, req_method, callback)
rpc_cb = self._generate_local_callback( self.rpc.register_method(ws_method, rpc_cb)
api_def.endpoint, r_method, callback)
else: def register_remote_handler(self, api_def):
# Callback is a remote method ws_method = api_def.ws_methods[0]
rpc_cb = self._generate_callback(api_def.endpoint) rpc_cb = self._generate_callback(api_def.endpoint)
self.rpc.register_method(cmd, rpc_cb) self.rpc.register_method(ws_method, rpc_cb)
def remove_handler(self, ws_method): def remove_handler(self, ws_method):
for prefix in ["get", "post", "delete"]: self.rpc.remove_method(ws_method)
self.rpc.remove_method(prefix + "_" + ws_method)
def _generate_callback(self, endpoint): def _generate_callback(self, endpoint):
async def func(**kwargs): async def func(**kwargs):