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.
This commit is contained in:
Fabio Manganiello 2023-05-20 15:26:58 +02:00
parent 013274bcbc
commit 666ea9ea6b
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
3 changed files with 48 additions and 22 deletions

View file

@ -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)

View file

@ -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

View file

@ -6,7 +6,8 @@ main.db:
backend.http:
port: 8123
disable_websocket: True
num_workers: 1
use_werkzeug_server: True
backend.redis:
disabled: False