From 666ea9ea6b5516ef1f5d3e519ae1b558555d057b Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Sat, 20 May 2023 15:26:58 +0200 Subject: [PATCH] Added `use_werkzeug_server` option to `backend.http`. There are situations where you may not want to run the HTTP server in a full blown WSGI-over-Tornado container - unit/integration tests and embedded single-core devices are among those cases. In those scenarios, we should allow the user to be able to run the backend using the built-in Werkzeug server provided by Flask. --- platypush/backend/http/__init__.py | 37 ++++++++++++++++++++++++------ tests/conftest.py | 30 +++++++++++++----------- tests/etc/config_test.yaml | 3 ++- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/platypush/backend/http/__init__.py b/platypush/backend/http/__init__.py index 67caf49a2a..a4879c961b 100644 --- a/platypush/backend/http/__init__.py +++ b/platypush/backend/http/__init__.py @@ -199,6 +199,7 @@ class HttpBackend(Backend): resource_dirs: Optional[Mapping[str, str]] = None, secret_key_file: Optional[str] = None, num_workers: Optional[int] = None, + use_werkzeug_server: bool = False, **kwargs, ): """ @@ -211,6 +212,16 @@ class HttpBackend(Backend): :param secret_key_file: Path to the file containing the secret key that will be used by Flask (default: ``~/.local/share/platypush/flask.secret.key``). :param num_workers: Number of worker processes to use (default: ``(cpu_count * 2) + 1``). + :param use_werkzeug_server: Whether the backend should be served by a + Werkzeug server (default: ``False``). Note that using the built-in + Werkzeug server instead of Tornado is very inefficient, and it + doesn't support websocket-based features either so the UI will + probably be severely limited. You should only use this option if: + + - You are running tests. + - You have issues with running a full Tornado server - for + example, you are running the application on a small embedded + device that doesn't support Tornado. """ super().__init__(**kwargs) @@ -235,6 +246,7 @@ class HttpBackend(Backend): ) self.local_base_url = f'http://localhost:{self.port}' self.num_workers = num_workers or (cpu_count() * 2) + 1 + self.use_werkzeug_server = use_werkzeug_server def send_message(self, *_, **__): self.logger.warning('Use cURL or any HTTP client to query the HTTP backend') @@ -339,14 +351,25 @@ class HttpBackend(Backend): self.num_workers, ) - sockets = bind_sockets(self.port, address=self.bind_address, reuse_port=True) + if self.use_werkzeug_server: + application.config['redis_queue'] = self.bus.redis_queue + application.run( + host=self.bind_address, + port=self.port, + use_reloader=False, + debug=True, + ) + else: + sockets = bind_sockets( + self.port, address=self.bind_address, reuse_port=True + ) - try: - fork_processes(self.num_workers) - future = self._post_fork_main(sockets) - asyncio.run(future) - except (asyncio.CancelledError, KeyboardInterrupt): - return + try: + fork_processes(self.num_workers) + future = self._post_fork_main(sockets) + asyncio.run(future) + except (asyncio.CancelledError, KeyboardInterrupt): + return def _start_web_server(self): self._server_proc = Process(target=self._web_server_proc) diff --git a/tests/conftest.py b/tests/conftest.py index 223a823f9c..5c31dd0b42 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,10 @@ import logging import os -import pytest import time from threading import Thread +import pytest + from platypush import Daemon, Config from .utils import config_file, set_base_url @@ -17,8 +18,6 @@ def clear_loggers(): This is to prevent pytest spitting out logging errors on teardown if the logging objects have been deinitialized (see https://github.com/pytest-dev/pytest/issues/5502#issuecomment-647157873). """ - import logging - # noinspection PyUnresolvedReferences loggers = [logging.getLogger()] + list(logging.Logger.manager.loggerDict.values()) for logger in loggers: handlers = getattr(logger, 'handlers', []) @@ -31,31 +30,34 @@ def app(): logging.info('Starting Platypush test service') Config.init(config_file) - app = Daemon(config_file=config_file, redis_queue='platypush-tests/bus') - Thread(target=lambda: app.run()).start() - logging.info('Sleeping {} seconds while waiting for the daemon to start up'.format(app_start_timeout)) + _app = Daemon(config_file=config_file, redis_queue='platypush-tests/bus') + Thread(target=_app.run).start() + logging.info( + 'Sleeping %d seconds while waiting for the daemon to start up', + app_start_timeout, + ) time.sleep(app_start_timeout) - yield app + yield _app logging.info('Stopping Platypush test service') - app.stop_app() + _app.stop_app() clear_loggers() - db_file = (Config.get('main.db') or {}).get('engine', '')[len('sqlite:///'):] + db = (Config.get('main.db') or {}).get('engine', '')[len('sqlite:///') :] - if db_file and os.path.isfile(db_file): - logging.info('Removing temporary db file {}'.format(db_file)) - os.unlink(db_file) + if db and os.path.isfile(db): + logging.info('Removing temporary db file %s', db) + os.unlink(db) @pytest.fixture(scope='session') def db_file(): - yield Config.get('main.db')['engine'][len('sqlite:///'):] + yield Config.get('main.db')['engine'][len('sqlite:///') :] @pytest.fixture(scope='session') def base_url(): backends = Config.get_backends() assert 'http' in backends, 'Missing HTTP server configuration' - url = 'http://localhost:{port}'.format(port=backends['http']['port']) + url = f'http://localhost:{backends["http"]["port"]}' set_base_url(url) yield url diff --git a/tests/etc/config_test.yaml b/tests/etc/config_test.yaml index bf8f5a156c..1859e0ae5f 100644 --- a/tests/etc/config_test.yaml +++ b/tests/etc/config_test.yaml @@ -6,7 +6,8 @@ main.db: backend.http: port: 8123 - disable_websocket: True + num_workers: 1 + use_werkzeug_server: True backend.redis: disabled: False