Beispiel #1
0
    def __init__(self, config):
        self.server = config.get_server()
        self.tornado_server = None
        self.api_cache = {}
        self.registered_base_handlers = []
        self.max_upload_size = config.getint('max_upload_size', 200)
        self.max_upload_size *= 1024 * 1024

        # Set Up Websocket and Authorization Managers
        self.wsm = WebsocketManager(self.server)
        self.auth = Authorization(config['authorization'])

        mimetypes.add_type('text/plain', '.log')
        mimetypes.add_type('text/plain', '.gcode')
        mimetypes.add_type('text/plain', '.cfg')
        debug = config.getboolean('enable_debug_logging', True)

        # Set up HTTP only requests
        self.mutable_router = MutableRouter(self)
        app_handlers = [(AnyMatches(), self.mutable_router),
                        (r"/websocket", WebSocket),
                        (r"/api/version", EmulateOctoprintHandler)]

        self.app = tornado.web.Application(app_handlers,
                                           serve_traceback=debug,
                                           websocket_ping_interval=10,
                                           websocket_ping_timeout=30,
                                           parent=self)
        self.get_handler_delegate = self.app.get_handler_delegate

        # Register handlers
        logfile = config['cmd_args'].get('logfile')
        self.register_static_file_handler("moonraker.log", logfile)
        self.auth.register_handlers(self)
Beispiel #2
0
    def __init__(self, config: ConfigHelper) -> None:
        self.server = config.get_server()
        self.http_server: Optional[HTTPServer] = None
        self.secure_server: Optional[HTTPServer] = None
        self.api_cache: Dict[str, APIDefinition] = {}
        self.registered_base_handlers: List[str] = []
        self.max_upload_size = config.getint('max_upload_size', 1024)
        self.max_upload_size *= 1024 * 1024

        # SSL config
        self.cert_path: str = self._get_path_option(config,
                                                    'ssl_certificate_path')
        self.key_path: str = self._get_path_option(config, 'ssl_key_path')

        # Set Up Websocket and Authorization Managers
        self.wsm = WebsocketManager(self.server)

        mimetypes.add_type('text/plain', '.log')
        mimetypes.add_type('text/plain', '.gcode')
        mimetypes.add_type('text/plain', '.cfg')

        self.debug = config.getboolean('enable_debug_logging', False)
        log_level = logging.DEBUG if self.debug else logging.INFO
        logging.getLogger().setLevel(log_level)
        app_args: Dict[str, Any] = {
            'serve_traceback': self.debug,
            'websocket_ping_interval': 10,
            'websocket_ping_timeout': 30,
            'parent': self,
            'default_handler_class': AuthorizedErrorHandler,
            'default_handler_args': {},
            'log_function': self.log_request
        }

        # Set up HTTP only requests
        self.mutable_router = MutableRouter(self)
        app_handlers: List[Any] = [(AnyMatches(), self.mutable_router),
                                   (r"/websocket", WebSocket),
                                   (r"/server/redirect", RedirectHandler)]
        self.app = tornado.web.Application(app_handlers, **app_args)
        self.get_handler_delegate = self.app.get_handler_delegate

        # Register handlers
        logfile = config['system_args'].get('logfile')
        if logfile:
            self.register_static_file_handler("moonraker.log",
                                              logfile,
                                              force=True)
        self.register_static_file_handler("klippy.log",
                                          DEFAULT_KLIPPY_LOG_PATH,
                                          force=True)
Beispiel #3
0
    def __init__(self, config):
        self.server = config.get_server()
        self.tornado_server = None
        self.api_cache = {}
        self.registered_base_handlers = []
        self.max_upload_size = config.getint('max_upload_size', 1024)
        self.max_upload_size *= 1024 * 1024

        # Set Up Websocket and Authorization Managers
        self.wsm = WebsocketManager(self.server)
        self.auth = Authorization(config['authorization'])

        mimetypes.add_type('text/plain', '.log')
        mimetypes.add_type('text/plain', '.gcode')
        mimetypes.add_type('text/plain', '.cfg')
        debug = config.getboolean('enable_debug_logging', False)
        log_level = logging.DEBUG if debug else logging.INFO
        logging.getLogger().setLevel(log_level)
        app_args = {
            'serve_traceback': debug,
            'websocket_ping_interval': 10,
            'websocket_ping_timeout': 30,
            'parent': self,
            'default_handler_class': AuthorizedErrorHandler,
            'default_handler_args': {}
        }
        if not debug:
            app_args['log_function'] = self.log_release_mode

        # Set up HTTP only requests
        self.mutable_router = MutableRouter(self)
        app_handlers = [(AnyMatches(), self.mutable_router),
                        (r"/websocket", WebSocket)]
        self.app = tornado.web.Application(app_handlers, **app_args)
        self.get_handler_delegate = self.app.get_handler_delegate

        # Register handlers
        logfile = config['system_args'].get('logfile')
        if logfile:
            self.register_static_file_handler("moonraker.log",
                                              logfile,
                                              force=True)
        self.register_static_file_handler("klippy.log",
                                          DEFAULT_KLIPPY_LOG_PATH,
                                          force=True)
        self.auth.register_handlers(self)
Beispiel #4
0
    def __init__(self, server, args):
        self.server = server
        self.tornado_server = None
        self.api_cache = {}
        self.registered_base_handlers = []

        # Set Up Websocket and Authorization Managers
        self.wsm = WebsocketManager(server)
        self.auth = Authorization(args.apikey)

        mimetypes.add_type('text/plain', '.log')
        mimetypes.add_type('text/plain', '.gcode')
        mimetypes.add_type('text/plain', '.cfg')

        # Set up HTTP only requests
        self.mutable_router = MutableRouter(self)
        app_handlers = [(AnyMatches(), self.mutable_router),
                        (r"/websocket", WebSocket, {
                            'wsm': self.wsm,
                            'auth': self.auth
                        }),
                        (r"/api/version", EmulateOctoprintHandler, {
                            'server': server,
                            'auth': self.auth
                        })]

        self.app = tornado.web.Application(app_handlers,
                                           serve_traceback=args.debug,
                                           websocket_ping_interval=10,
                                           websocket_ping_timeout=30,
                                           enable_cors=False)
        self.get_handler_delegate = self.app.get_handler_delegate

        # Register handlers
        self.register_static_file_handler("moonraker.log", args.logfile)
        self.auth.register_handlers(self)
Beispiel #5
0
class MoonrakerApp:
    def __init__(self, config):
        self.server = config.get_server()
        self.tornado_server = None
        self.api_cache = {}
        self.registered_base_handlers = []
        self.max_upload_size = config.getint('max_upload_size', 200)
        self.max_upload_size *= 1024 * 1024

        # Set Up Websocket and Authorization Managers
        self.wsm = WebsocketManager(self.server)
        self.auth = Authorization(config['authorization'])

        mimetypes.add_type('text/plain', '.log')
        mimetypes.add_type('text/plain', '.gcode')
        mimetypes.add_type('text/plain', '.cfg')
        debug = config.getboolean('enable_debug_logging', True)

        # Set up HTTP only requests
        self.mutable_router = MutableRouter(self)
        app_handlers = [(AnyMatches(), self.mutable_router),
                        (r"/websocket", WebSocket),
                        (r"/api/version", EmulateOctoprintHandler)]

        self.app = tornado.web.Application(app_handlers,
                                           serve_traceback=debug,
                                           websocket_ping_interval=10,
                                           websocket_ping_timeout=30,
                                           parent=self)
        self.get_handler_delegate = self.app.get_handler_delegate

        # Register handlers
        logfile = config['cmd_args'].get('logfile')
        self.register_static_file_handler("moonraker.log", logfile)
        self.auth.register_handlers(self)

    def listen(self, host, port):
        self.tornado_server = self.app.listen(
            port,
            address=host,
            max_body_size=self.max_upload_size,
            xheaders=True)

    def get_server(self):
        return self.server

    def get_auth(self):
        return self.auth

    def get_websocket_manager(self):
        return self.wsm

    async def close(self):
        if self.tornado_server is not None:
            self.tornado_server.stop()
            await self.tornado_server.close_all_connections()
        await self.wsm.close()
        self.auth.close()

    def register_remote_handler(self, endpoint):
        if endpoint in RESERVED_ENDPOINTS:
            return
        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"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['arg_parser'] = api_def.parser
        params['remote_callback'] = api_def.endpoint
        self.mutable_router.add_handler(api_def.uri, RemoteRequestHandler,
                                        params)
        self.registered_base_handlers.append(api_def.uri)

    def register_local_handler(self,
                               uri,
                               request_methods,
                               callback,
                               protocol=["http", "websocket"]):
        if uri in self.registered_base_handlers:
            return
        api_def = self._create_api_definition(uri,
                                              request_methods,
                                              is_remote=False)
        msg = "Registering local endpoint"
        if "http" in protocol:
            msg += f" - HTTP: ({' '.join(request_methods)}) {uri}"
            params = {}
            params['methods'] = request_methods
            params['arg_parser'] = api_def.parser
            params['callback'] = callback
            self.mutable_router.add_handler(uri, LocalRequestHandler, params)
            self.registered_base_handlers.append(uri)
        if "websocket" in protocol:
            msg += f" - Websocket: {', '.join(api_def.ws_methods)}"
            self.wsm.register_local_handler(api_def, callback)
        logging.info(msg)

    def register_static_file_handler(self, pattern, file_path):
        if pattern[0] != "/":
            pattern = "/server/files/" + pattern
        if os.path.isfile(file_path):
            pattern += '()'
        elif os.path.isdir(file_path):
            if pattern[-1] != "/":
                pattern += "/"
            pattern += "(.*)"
        else:
            logging.info(f"Invalid file path: {file_path}")
            return
        logging.debug(f"Registering static file: ({pattern}) {file_path}")
        params = {'path': file_path}
        self.mutable_router.add_handler(pattern, FileRequestHandler, params)

    def register_upload_handler(self, pattern):
        self.mutable_router.add_handler(pattern, FileUploadHandler, {})

    def remove_handler(self, endpoint):
        api_def = self.api_cache.get(endpoint)
        if api_def is not None:
            self.wsm.remove_handler(api_def.uri)
            self.mutable_router.remove_handler(api_def.ws_method)

    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
        elif is_remote:
            uri = "/printer/" + endpoint
        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_methods, request_methods,
                                parser)
        self.api_cache[endpoint] = api_def
        return api_def
Beispiel #6
0
class MoonrakerApp:
    def __init__(self, config: ConfigHelper) -> None:
        self.server = config.get_server()
        self.http_server: Optional[HTTPServer] = None
        self.secure_server: Optional[HTTPServer] = None
        self.api_cache: Dict[str, APIDefinition] = {}
        self.registered_base_handlers: List[str] = []
        self.max_upload_size = config.getint('max_upload_size', 1024)
        self.max_upload_size *= 1024 * 1024

        # SSL config
        self.cert_path: str = self._get_path_option(config,
                                                    'ssl_certificate_path')
        self.key_path: str = self._get_path_option(config, 'ssl_key_path')

        # Set Up Websocket and Authorization Managers
        self.wsm = WebsocketManager(self.server)

        mimetypes.add_type('text/plain', '.log')
        mimetypes.add_type('text/plain', '.gcode')
        mimetypes.add_type('text/plain', '.cfg')

        self.debug = config.getboolean('enable_debug_logging', False)
        log_level = logging.DEBUG if self.debug else logging.INFO
        logging.getLogger().setLevel(log_level)
        app_args: Dict[str, Any] = {
            'serve_traceback': self.debug,
            'websocket_ping_interval': 10,
            'websocket_ping_timeout': 30,
            'parent': self,
            'default_handler_class': AuthorizedErrorHandler,
            'default_handler_args': {},
            'log_function': self.log_request
        }

        # Set up HTTP only requests
        self.mutable_router = MutableRouter(self)
        app_handlers: List[Any] = [(AnyMatches(), self.mutable_router),
                                   (r"/websocket", WebSocket),
                                   (r"/server/redirect", RedirectHandler)]
        self.app = tornado.web.Application(app_handlers, **app_args)
        self.get_handler_delegate = self.app.get_handler_delegate

        # Register handlers
        logfile = config['system_args'].get('logfile')
        if logfile:
            self.register_static_file_handler("moonraker.log",
                                              logfile,
                                              force=True)
        self.register_static_file_handler("klippy.log",
                                          DEFAULT_KLIPPY_LOG_PATH,
                                          force=True)

    def _get_path_option(self, config: ConfigHelper, option: str) -> str:
        path: Optional[str] = config.get(option, None)
        if path is None:
            return ""
        expanded = os.path.abspath(os.path.expanduser(path))
        if not os.path.exists(expanded):
            raise self.server.error(f"Invalid path for option '{option}', "
                                    f"{path} does not exist")
        return expanded

    def listen(self, host: str, port: int, ssl_port: int) -> None:
        self.http_server = self.app.listen(port,
                                           address=host,
                                           max_body_size=MAX_BODY_SIZE,
                                           xheaders=True)
        if os.path.exists(self.cert_path) and os.path.exists(self.key_path):
            logging.info(f"Starting secure server on port {ssl_port}")
            ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
            ssl_ctx.load_cert_chain(self.cert_path, self.key_path)
            self.secure_server = self.app.listen(ssl_port,
                                                 address=host,
                                                 max_body_size=MAX_BODY_SIZE,
                                                 xheaders=True,
                                                 ssl_options=ssl_ctx)
        else:
            logging.info("SSL Certificate/Key not configured, "
                         "aborting HTTPS Server startup")

    def log_request(self, handler: tornado.web.RequestHandler) -> None:
        status_code = handler.get_status()
        if not self.debug and status_code in [200, 204, 206, 304]:
            # don't log successful requests in release mode
            return
        if status_code < 400:
            log_method = access_log.info
        elif status_code < 500:
            log_method = access_log.warning
        else:
            log_method = access_log.error
        request_time = 1000.0 * handler.request.request_time()
        user = handler.current_user
        username = "******"
        if user is not None and 'username' in user:
            username = user['username']
        log_method(f"{status_code} {handler._request_summary()} "
                   f"[{username}] {request_time:.2f}ms")

    def get_server(self) -> Server:
        return self.server

    def get_websocket_manager(self) -> WebsocketManager:
        return self.wsm

    async def close(self) -> None:
        if self.http_server is not None:
            self.http_server.stop()
            await self.http_server.close_all_connections()
        if self.secure_server is not None:
            self.secure_server.stop()
            await self.secure_server.close_all_connections()
        await self.wsm.close()

    def register_remote_handler(self, endpoint: str) -> None:
        if endpoint in RESERVED_ENDPOINTS:
            return
        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"HTTP: ({' '.join(api_def.request_methods)}) {api_def.uri}; "
            f"Websocket: {', '.join(api_def.ws_methods)}")
        self.wsm.register_remote_handler(api_def)
        params: Dict[str, Any] = {}
        params['methods'] = api_def.request_methods
        params['callback'] = api_def.endpoint
        params['need_object_parser'] = api_def.need_object_parser
        self.mutable_router.add_handler(api_def.uri, DynamicRequestHandler,
                                        params)
        self.registered_base_handlers.append(api_def.uri)

    def register_local_handler(self,
                               uri: str,
                               request_methods: List[str],
                               callback: Callable[[WebRequest], Coroutine],
                               protocol: List[str] = ["http", "websocket"],
                               wrap_result: bool = True) -> None:
        if uri in self.registered_base_handlers:
            return
        api_def = self._create_api_definition(uri,
                                              request_methods,
                                              is_remote=False)
        msg = "Registering local endpoint"
        if "http" in protocol:
            msg += f" - HTTP: ({' '.join(request_methods)}) {uri}"
            params: dict[str, Any] = {}
            params['methods'] = request_methods
            params['callback'] = callback
            params['wrap_result'] = wrap_result
            params['is_remote'] = False
            self.mutable_router.add_handler(uri, DynamicRequestHandler, params)
            self.registered_base_handlers.append(uri)
        if "websocket" in protocol:
            msg += f" - Websocket: {', '.join(api_def.ws_methods)}"
            self.wsm.register_local_handler(api_def, callback)
        logging.info(msg)

    def register_static_file_handler(self,
                                     pattern: str,
                                     file_path: str,
                                     force: bool = False) -> None:
        if pattern[0] != "/":
            pattern = "/server/files/" + pattern
        if os.path.isfile(file_path) or force:
            pattern += '()'
        elif os.path.isdir(file_path):
            if pattern[-1] != "/":
                pattern += "/"
            pattern += "(.*)"
        else:
            logging.info(f"Invalid file path: {file_path}")
            return
        logging.debug(f"Registering static file: ({pattern}) {file_path}")
        params = {'path': file_path}
        self.mutable_router.add_handler(pattern, FileRequestHandler, params)

    def register_upload_handler(self, pattern: str) -> None:
        self.mutable_router.add_handler(
            pattern, FileUploadHandler,
            {'max_upload_size': self.max_upload_size})

    def remove_handler(self, endpoint: str) -> None:
        api_def = self.api_cache.get(endpoint)
        if api_def is not None:
            self.mutable_router.remove_handler(api_def.uri)
            for ws_method in api_def.ws_methods:
                self.wsm.remove_handler(ws_method)

    def _create_api_definition(self,
                               endpoint: str,
                               request_methods: List[str] = [],
                               is_remote=True) -> APIDefinition:
        if endpoint in self.api_cache:
            return self.api_cache[endpoint]
        if endpoint[0] == '/':
            uri = endpoint
        elif is_remote:
            uri = "/printer/" + endpoint
        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")
        need_object_parser = endpoint.startswith("objects/")
        api_def = APIDefinition(endpoint, uri, ws_methods, request_methods,
                                need_object_parser)
        self.api_cache[endpoint] = api_def
        return api_def
Beispiel #7
0
class MoonrakerApp:
    def __init__(self, server, args):
        self.server = server
        self.tornado_server = None
        self.api_cache = {}
        self.registered_base_handlers = []

        # Set Up Websocket and Authorization Managers
        self.wsm = WebsocketManager(server)
        self.auth = Authorization(args.apikey)

        mimetypes.add_type('text/plain', '.log')
        mimetypes.add_type('text/plain', '.gcode')
        mimetypes.add_type('text/plain', '.cfg')

        # Set up HTTP only requests
        self.mutable_router = MutableRouter(self)
        app_handlers = [(AnyMatches(), self.mutable_router),
                        (r"/websocket", WebSocket, {
                            'wsm': self.wsm,
                            'auth': self.auth
                        }),
                        (r"/api/version", EmulateOctoprintHandler, {
                            'server': server,
                            'auth': self.auth
                        })]

        self.app = tornado.web.Application(app_handlers,
                                           serve_traceback=args.debug,
                                           websocket_ping_interval=10,
                                           websocket_ping_timeout=30,
                                           enable_cors=False)
        self.get_handler_delegate = self.app.get_handler_delegate

        # Register handlers
        self.register_static_file_handler("moonraker.log", args.logfile)
        self.auth.register_handlers(self)

    def listen(self, host, port):
        self.tornado_server = self.app.listen(port,
                                              address=host,
                                              max_body_size=MAX_UPLOAD_SIZE,
                                              xheaders=True)

    async def close(self):
        if self.tornado_server is not None:
            self.tornado_server.stop()
        await self.wsm.close()
        self.auth.close()

    def load_config(self, config):
        if 'enable_cors' in config:
            self.app.settings['enable_cors'] = config['enable_cors']
        self.auth.load_config(config)

    def register_remote_handler(self, endpoint):
        if endpoint in RESERVED_ENDPOINTS:
            return
        api_def = self.api_cache.get(endpoint,
                                     self._create_api_definition(endpoint))
        if api_def.uri in self.registered_base_handlers:
            # reserved handler or already registered
            return
        logging.info("Registering remote endpoint: (%s) %s" %
                     (" ".join(api_def.request_methods), api_def.uri))
        self.wsm.register_handler(api_def)
        params = {}
        params['server'] = self.server
        params['auth'] = self.auth
        params['methods'] = api_def.request_methods
        params['arg_parser'] = api_def.parser
        params['remote_callback'] = api_def.endpoint
        self.mutable_router.add_handler(api_def.uri, RemoteRequestHandler,
                                        params)
        self.registered_base_handlers.append(api_def.uri)

    def register_local_handler(self,
                               uri,
                               ws_method,
                               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("Registering local endpoint: (%s) %s" %
                     (" ".join(request_methods), uri))
        if not http_only:
            self.wsm.register_handler(api_def, callback)
        params = {}
        params['server'] = self.server
        params['auth'] = self.auth
        params['methods'] = request_methods
        params['arg_parser'] = api_def.parser
        params['callback'] = callback
        self.mutable_router.add_handler(uri, LocalRequestHandler, params)
        self.registered_base_handlers.append(uri)

    def register_static_file_handler(self,
                                     pattern,
                                     file_path,
                                     can_delete=False,
                                     op_check_cb=None):
        if pattern[0] != "/":
            pattern = "/server/files/" + pattern
        if os.path.isfile(file_path):
            pattern += '()'
        elif os.path.isdir(file_path):
            if pattern[-1] != "/":
                pattern += "/"
            pattern += "(.*)"
        else:
            logging.info("Invalid file path: %s" % (file_path))
            return
        methods = ['GET']
        if can_delete:
            methods.append('DELETE')
        params = {
            'server': self.server,
            'auth': self.auth,
            'path': file_path,
            'methods': methods,
            'op_check_cb': op_check_cb
        }
        self.mutable_router.add_handler(pattern, FileRequestHandler, params)

    def register_upload_handler(self, pattern):
        params = {'server': self.server, 'auth': self.auth}
        self.mutable_router.add_handler(pattern, FileUploadHandler, params)

    def remove_handler(self, endpoint):
        api_def = self.api_cache.get(endpoint)
        if api_def is not None:
            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']):
        if endpoint in self.api_cache:
            return self.api_cache[endpoint]
        if endpoint[0] == '/':
            uri = endpoint
        else:
            uri = "/printer/" + endpoint
        if ws_method is None:
            ws_method = uri[1:].replace('/', '_')
        if endpoint.startswith("objects/"):
            parser = _status_parser
        else:
            parser = _default_parser
        api_def = APIDefinition(endpoint, uri, ws_method, request_methods,
                                parser)
        self.api_cache[endpoint] = api_def
        return api_def
Beispiel #8
0
class MoonrakerApp:
    def __init__(self, config):
        self.server = config.get_server()
        self.tornado_server = None
        self.api_cache = {}
        self.registered_base_handlers = []
        self.max_upload_size = config.getint('max_upload_size', 1024)
        self.max_upload_size *= 1024 * 1024

        # Set Up Websocket and Authorization Managers
        self.wsm = WebsocketManager(self.server)
        self.auth = Authorization(config['authorization'])

        mimetypes.add_type('text/plain', '.log')
        mimetypes.add_type('text/plain', '.gcode')
        mimetypes.add_type('text/plain', '.cfg')
        debug = config.getboolean('enable_debug_logging', False)
        log_level = logging.DEBUG if debug else logging.INFO
        logging.getLogger().setLevel(log_level)
        app_args = {
            'serve_traceback': debug,
            'websocket_ping_interval': 10,
            'websocket_ping_timeout': 30,
            'parent': self,
            'default_handler_class': AuthorizedErrorHandler,
            'default_handler_args': {}
        }
        if not debug:
            app_args['log_function'] = self.log_release_mode

        # Set up HTTP only requests
        self.mutable_router = MutableRouter(self)
        app_handlers = [(AnyMatches(), self.mutable_router),
                        (r"/websocket", WebSocket)]
        self.app = tornado.web.Application(app_handlers, **app_args)
        self.get_handler_delegate = self.app.get_handler_delegate

        # Register handlers
        logfile = config['system_args'].get('logfile')
        if logfile:
            self.register_static_file_handler("moonraker.log",
                                              logfile,
                                              force=True)
        self.register_static_file_handler("klippy.log",
                                          DEFAULT_KLIPPY_LOG_PATH,
                                          force=True)
        self.auth.register_handlers(self)

    def listen(self, host, port):
        self.tornado_server = self.app.listen(port,
                                              address=host,
                                              max_body_size=MAX_BODY_SIZE,
                                              xheaders=True)

    def log_release_mode(self, handler):
        status_code = handler.get_status()
        if status_code in [200, 204]:
            # don't log OK and No Content
            return
        if status_code < 400:
            log_method = access_log.info
        elif status_code < 500:
            log_method = access_log.warning
        else:
            log_method = access_log.error
        request_time = 1000.0 * handler.request.request_time()
        log_method("%d %s %.2fms", status_code, handler._request_summary(),
                   request_time)

    def get_server(self):
        return self.server

    def get_auth(self):
        return self.auth

    def get_websocket_manager(self):
        return self.wsm

    async def close(self):
        if self.tornado_server is not None:
            self.tornado_server.stop()
            await self.tornado_server.close_all_connections()
        await self.wsm.close()
        self.auth.close()

    def register_remote_handler(self, endpoint):
        if endpoint in RESERVED_ENDPOINTS:
            return
        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"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['methods'] = api_def.request_methods
        params['callback'] = api_def.endpoint
        params['need_object_parser'] = api_def.need_object_parser
        self.mutable_router.add_handler(api_def.uri, DynamicRequestHandler,
                                        params)
        self.registered_base_handlers.append(api_def.uri)

    def register_local_handler(self,
                               uri,
                               request_methods,
                               callback,
                               protocol=["http", "websocket"],
                               wrap_result=True):
        if uri in self.registered_base_handlers:
            return
        api_def = self._create_api_definition(uri,
                                              request_methods,
                                              is_remote=False)
        msg = "Registering local endpoint"
        if "http" in protocol:
            msg += f" - HTTP: ({' '.join(request_methods)}) {uri}"
            params = {}
            params['methods'] = request_methods
            params['callback'] = callback
            params['wrap_result'] = wrap_result
            params['is_remote'] = False
            self.mutable_router.add_handler(uri, DynamicRequestHandler, params)
            self.registered_base_handlers.append(uri)
        if "websocket" in protocol:
            msg += f" - Websocket: {', '.join(api_def.ws_methods)}"
            self.wsm.register_local_handler(api_def, callback)
        logging.info(msg)

    def register_static_file_handler(self, pattern, file_path, force=False):
        if pattern[0] != "/":
            pattern = "/server/files/" + pattern
        if os.path.isfile(file_path) or force:
            pattern += '()'
        elif os.path.isdir(file_path):
            if pattern[-1] != "/":
                pattern += "/"
            pattern += "(.*)"
        else:
            logging.info(f"Invalid file path: {file_path}")
            return
        logging.debug(f"Registering static file: ({pattern}) {file_path}")
        params = {'path': file_path}
        self.mutable_router.add_handler(pattern, FileRequestHandler, params)

    def register_upload_handler(self, pattern):
        self.mutable_router.add_handler(
            pattern, FileUploadHandler,
            {'max_upload_size': self.max_upload_size})

    def remove_handler(self, endpoint):
        api_def = self.api_cache.get(endpoint)
        if api_def is not None:
            self.wsm.remove_handler(api_def.uri)
            self.mutable_router.remove_handler(api_def.ws_method)

    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
        elif is_remote:
            uri = "/printer/" + endpoint
        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")
        need_object_parser = endpoint.startswith("objects/")
        api_def = APIDefinition(endpoint, uri, ws_methods, request_methods,
                                need_object_parser)
        self.api_cache[endpoint] = api_def
        return api_def
Beispiel #9
0
    def __init__(self, config: ConfigHelper) -> None:
        self.server = config.get_server()
        self.http_server: Optional[HTTPServer] = None
        self.secure_server: Optional[HTTPServer] = None
        self.api_cache: Dict[str, APIDefinition] = {}
        self.registered_base_handlers: List[str] = []
        self.max_upload_size = config.getint('max_upload_size', 1024)
        self.max_upload_size *= 1024 * 1024

        # SSL config
        self.cert_path: str = self._get_path_option(
            config, 'ssl_certificate_path')
        self.key_path: str = self._get_path_option(
            config, 'ssl_key_path')

        # Set Up Websocket and Authorization Managers
        self.wsm = WebsocketManager(self.server)
        self.internal_transport = InternalTransport(self.server)
        self.api_transports: Dict[str, APITransport] = {
            "websocket": self.wsm,
            "internal": self.internal_transport
        }

        mimetypes.add_type('text/plain', '.log')
        mimetypes.add_type('text/plain', '.gcode')
        mimetypes.add_type('text/plain', '.cfg')

        self.debug = self.server.is_debug_enabled()
        app_args: Dict[str, Any] = {
            'serve_traceback': self.debug,
            'websocket_ping_interval': 10,
            'websocket_ping_timeout': 30,
            'server': self.server,
            'default_handler_class': AuthorizedErrorHandler,
            'default_handler_args': {},
            'log_function': self.log_request,
            'compiled_template_cache': False,
        }

        # Set up HTTP only requests
        self.mutable_router = MutableRouter(self)
        app_handlers: List[Any] = [
            (AnyMatches(), self.mutable_router),
            (r"/", WelcomeHandler),
            (r"/websocket", WebSocket),
            (r"/server/redirect", RedirectHandler)
        ]
        self.app = tornado.web.Application(app_handlers, **app_args)
        self.get_handler_delegate = self.app.get_handler_delegate

        # Register handlers
        logfile = self.server.get_app_args().get('log_file')
        if logfile:
            self.register_static_file_handler(
                "moonraker.log", logfile, force=True)
        self.register_static_file_handler(
            "klippy.log", DEFAULT_KLIPPY_LOG_PATH, force=True)
        self.register_upload_handler("/server/files/upload")

        # Register Server Components
        self.server.register_component("application", self)
        self.server.register_component("websockets", self.wsm)
        self.server.register_component("internal_transport",
                                       self.internal_transport)