def start_api(self): """ Start API ... should be run in thread. """ rest_ip = self._config['api_server']['listen_ip_address'] rest_port = self._config['api_server']['listen_port'] logger.info(f'Starting HTTP Server at {rest_ip}:{rest_port}') if not IPv4Address(rest_ip).is_loopback: logger.warning( "SECURITY WARNING - Local Rest Server listening to external connections" ) logger.warning( "SECURITY WARNING - This is insecure please set to your loopback," "e.g 127.0.0.1 in config.json") if not self._config['api_server'].get('password'): logger.warning( "SECURITY WARNING - No password for local REST Server defined. " "Please make sure that this is intentional!") if (self._config['api_server'].get('jwt_secret_key', 'super-secret') in ('super-secret, somethingrandom')): logger.warning( "SECURITY WARNING - `jwt_secret_key` seems to be default." "Others may be able to log into your bot.") logger.info('Starting Local Rest Server.') verbosity = self._config['api_server'].get('verbosity', 'error') uvconfig = uvicorn.Config( self.app, port=rest_port, host=rest_ip, use_colors=False, log_config=None, access_log=True if verbosity != 'error' else False, ) try: self._server = UvicornServer(uvconfig) if self._standalone: self._server.run() else: self._server.run_in_thread() except Exception: logger.exception("Api server failed to start.")
def test_api_UvicornServer(mocker): thread_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.threading.Thread') s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1')) assert thread_mock.call_count == 0 s.install_signal_handlers() # Original implementation starts a thread - make sure that's not the case assert thread_mock.call_count == 0 # Fake started to avoid sleeping forever s.started = True s.run_in_thread() assert thread_mock.call_count == 1 s.cleanup() assert s.should_exit is True
def test_api_UvicornServer_run_no_uvloop(mocker, import_fails): serve_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve', get_mock_coro(None)) s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1')) assert serve_mock.call_count == 0 s.install_signal_handlers() # Original implementation starts a thread - make sure that's not the case assert serve_mock.call_count == 0 # Fake started to avoid sleeping forever s.started = True s.run() assert serve_mock.call_count == 1
class ApiServer(RPCHandler): __instance = None __initialized = False _rpc: RPC # Backtesting type: Backtesting _bt = None _bt_data = None _bt_timerange = None _bt_last_config: Dict[str, Any] = {} _has_rpc: bool = False _bgtask_running: bool = False _config: Dict[str, Any] = {} def __new__(cls, *args, **kwargs): """ This class is a singleton. We'll only have one instance of it around. """ if ApiServer.__instance is None: ApiServer.__instance = object.__new__(cls) ApiServer.__initialized = False return ApiServer.__instance def __init__(self, config: Dict[str, Any], standalone: bool = False) -> None: ApiServer._config = config if self.__initialized and (standalone or self._standalone): return self._standalone: bool = standalone self._server = None ApiServer.__initialized = True api_config = self._config['api_server'] self.app = FastAPI( title="Freqtrade API", docs_url='/docs' if api_config.get('enable_openapi', False) else None, redoc_url=None, default_response_class=FTJSONResponse, ) self.configure_app(self.app, self._config) self.start_api() def add_rpc_handler(self, rpc: RPC): """ Attach rpc handler """ if not self._has_rpc: ApiServer._rpc = rpc ApiServer._has_rpc = True else: # This should not happen assuming we didn't mess up. raise OperationalException('RPC Handler already attached.') def cleanup(self) -> None: """ Cleanup pending module resources """ ApiServer._has_rpc = False del ApiServer._rpc if self._server and not self._standalone: logger.info("Stopping API Server") self._server.cleanup() @classmethod def shutdown(cls): cls.__initialized = False del cls.__instance cls.__instance = None cls._has_rpc = False cls._rpc = None def send_msg(self, msg: Dict[str, str]) -> None: pass def handle_rpc_exception(self, request, exc): logger.exception(f"API Error calling: {exc}") return JSONResponse( status_code=502, content={ 'error': f"Error querying {request.url.path}: {exc.message}" }) def configure_app(self, app: FastAPI, config): from freqtrade.rpc.api_server.api_auth import http_basic_or_jwt_token, router_login from freqtrade.rpc.api_server.api_backtest import router as api_backtest from freqtrade.rpc.api_server.api_v1 import router as api_v1 from freqtrade.rpc.api_server.api_v1 import router_public as api_v1_public from freqtrade.rpc.api_server.web_ui import router_ui app.include_router(api_v1_public, prefix="/api/v1") app.include_router( api_v1, prefix="/api/v1", dependencies=[Depends(http_basic_or_jwt_token)], ) app.include_router( api_backtest, prefix="/api/v1", dependencies=[Depends(http_basic_or_jwt_token)], ) app.include_router(router_login, prefix="/api/v1", tags=["auth"]) # UI Router MUST be last! app.include_router(router_ui, prefix='') app.add_middleware( CORSMiddleware, allow_origins=config['api_server'].get('CORS_origins', []), allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.add_exception_handler(RPCException, self.handle_rpc_exception) def start_api(self): """ Start API ... should be run in thread. """ rest_ip = self._config['api_server']['listen_ip_address'] rest_port = self._config['api_server']['listen_port'] logger.info(f'Starting HTTP Server at {rest_ip}:{rest_port}') if not IPv4Address(rest_ip).is_loopback: logger.warning( "SECURITY WARNING - Local Rest Server listening to external connections" ) logger.warning( "SECURITY WARNING - This is insecure please set to your loopback," "e.g 127.0.0.1 in config.json") if not self._config['api_server'].get('password'): logger.warning( "SECURITY WARNING - No password for local REST Server defined. " "Please make sure that this is intentional!") if (self._config['api_server'].get('jwt_secret_key', 'super-secret') in ('super-secret, somethingrandom')): logger.warning( "SECURITY WARNING - `jwt_secret_key` seems to be default." "Others may be able to log into your bot.") logger.info('Starting Local Rest Server.') verbosity = self._config['api_server'].get('verbosity', 'error') uvconfig = uvicorn.Config( self.app, port=rest_port, host=rest_ip, use_colors=False, log_config=None, access_log=True if verbosity != 'error' else False, ) try: self._server = UvicornServer(uvconfig) if self._standalone: self._server.run() else: self._server.run_in_thread() except Exception: logger.exception("Api server failed to start.")
class ApiServer(RPCHandler): _rpc: RPC _has_rpc: bool = False _config: Dict[str, Any] = {} def __init__(self, rpc: RPC, config: Dict[str, Any]) -> None: super().__init__(rpc, config) self._server = None ApiServer._rpc = rpc ApiServer._has_rpc = True ApiServer._config = config api_config = self._config['api_server'] self.app = FastAPI( title="Freqtrade API", docs_url='/docs' if api_config.get('enable_openapi', False) else None, redoc_url=None, default_response_class=FTJSONResponse, ) self.configure_app(self.app, self._config) self.start_api() def cleanup(self) -> None: """ Cleanup pending module resources """ if self._server: logger.info("Stopping API Server") self._server.cleanup() def send_msg(self, msg: Dict[str, str]) -> None: pass def handle_rpc_exception(self, request, exc): logger.exception(f"API Error calling: {exc}") return JSONResponse( status_code=502, content={ 'error': f"Error querying {request.url.path}: {exc.message}" }) def configure_app(self, app: FastAPI, config): from freqtrade.rpc.api_server.api_auth import http_basic_or_jwt_token, router_login from freqtrade.rpc.api_server.api_v1 import router as api_v1 from freqtrade.rpc.api_server.api_v1 import router_public as api_v1_public from freqtrade.rpc.api_server.web_ui import router_ui app.include_router(api_v1_public, prefix="/api/v1") app.include_router( api_v1, prefix="/api/v1", dependencies=[Depends(http_basic_or_jwt_token)], ) app.include_router(router_login, prefix="/api/v1", tags=["auth"]) # UI Router MUST be last! app.include_router(router_ui, prefix='') app.add_middleware( CORSMiddleware, allow_origins=config['api_server'].get('CORS_origins', []), allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.add_exception_handler(RPCException, self.handle_rpc_exception) def start_api(self): """ Start API ... should be run in thread. """ rest_ip = self._config['api_server']['listen_ip_address'] rest_port = self._config['api_server']['listen_port'] logger.info(f'Starting HTTP Server at {rest_ip}:{rest_port}') if not IPv4Address(rest_ip).is_loopback: logger.warning( "SECURITY WARNING - Local Rest Server listening to external connections" ) logger.warning( "SECURITY WARNING - This is insecure please set to your loopback," "e.g 127.0.0.1 in config.json") if not self._config['api_server'].get('password'): logger.warning( "SECURITY WARNING - No password for local REST Server defined. " "Please make sure that this is intentional!") if (self._config['api_server'].get('jwt_secret_key', 'super-secret') in ('super-secret, somethingrandom')): logger.warning( "SECURITY WARNING - `jwt_secret_key` seems to be default." "Others may be able to log into your bot.") logger.info('Starting Local Rest Server.') verbosity = self._config['api_server'].get('verbosity', 'error') log_config = uvicorn.config.LOGGING_CONFIG # Change logging of access logs to stderr log_config["handlers"]["access"]["stream"] = log_config["handlers"][ "default"]["stream"] uvconfig = uvicorn.Config( self.app, port=rest_port, host=rest_ip, use_colors=False, log_config=log_config, access_log=True if verbosity != 'error' else False, ) try: self._server = UvicornServer(uvconfig) self._server.run_in_thread() except Exception: logger.exception("Api server failed to start.")