Migrated from waitress to gunicorn.

`waitress`, unlike `gunicorn`, doesn't provide an easy way to plug into
a WSGI socket that can be used for the websocket interface.
This commit is contained in:
Fabio Manganiello 2023-05-07 00:42:57 +02:00
parent ca65db016e
commit 3aefc9607d
Signed by: blacklight
GPG key ID: D90FBA7F76362774
5 changed files with 48 additions and 37 deletions

View file

@ -201,6 +201,7 @@ autodoc_default_options = {
} }
autodoc_mock_imports = [ autodoc_mock_imports = [
'gunicorn',
'googlesamples.assistant.grpc.audio_helpers', 'googlesamples.assistant.grpc.audio_helpers',
'google.assistant.embedded', 'google.assistant.embedded',
'google.assistant.library', 'google.assistant.library',

View file

@ -3,7 +3,7 @@ import pathlib
import secrets import secrets
import threading import threading
from multiprocessing import Process from multiprocessing import Process, cpu_count
try: try:
from websockets.exceptions import ConnectionClosed # type: ignore from websockets.exceptions import ConnectionClosed # type: ignore
@ -11,10 +11,9 @@ try:
except ImportError: except ImportError:
from websockets import ConnectionClosed, serve as websocket_serve # type: ignore from websockets import ConnectionClosed, serve as websocket_serve # type: ignore
import waitress
from platypush.backend import Backend from platypush.backend import Backend
from platypush.backend.http.app import application from platypush.backend.http.app import application
from platypush.backend.http.wsgi import WSGIApplicationWrapper
from platypush.bus.redis import RedisBus from platypush.bus.redis import RedisBus
from platypush.config import Config from platypush.config import Config
from platypush.context import get_or_create_event_loop from platypush.context import get_or_create_event_loop
@ -174,7 +173,6 @@ class HttpBackend(Backend):
ssl_capath=None, ssl_capath=None,
maps=None, maps=None,
secret_key_file=None, secret_key_file=None,
flask_args=None,
**kwargs, **kwargs,
): ):
""" """
@ -213,9 +211,6 @@ class HttpBackend(Backend):
:param secret_key_file: Path to the file containing the secret key that will be used by Flask :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``). (default: ``~/.local/share/platypush/flask.secret.key``).
:type secret_key_file: str :type secret_key_file: str
:param flask_args: Extra key-value arguments that should be passed to the Flask service.
:type flask_args: dict[str, str]
""" """
super().__init__(**kwargs) super().__init__(**kwargs)
@ -239,7 +234,6 @@ class HttpBackend(Backend):
self.resource_dirs = {} self.resource_dirs = {}
self.active_websockets = set() self.active_websockets = set()
self.flask_args = flask_args or {}
self.ssl_context = ( self.ssl_context = (
get_ssl_server_context( get_ssl_server_context(
ssl_cert=ssl_cert, ssl_cert=ssl_cert,
@ -420,24 +414,18 @@ class HttpBackend(Backend):
def _web_server_proc(self): def _web_server_proc(self):
def proc(): def proc():
self.logger.info('Starting local web server on port %s', self.port) self.logger.info('Starting local web server on port %s', self.port)
kwargs = {
'host': self.bind_address,
'port': self.port,
}
assert isinstance( assert isinstance(
self.bus, RedisBus self.bus, RedisBus
), 'The HTTP backend only works if backed by a Redis bus' ), 'The HTTP backend only works if backed by a Redis bus'
application.config['redis_queue'] = self.bus.redis_queue application.config['redis_queue'] = self.bus.redis_queue
application.secret_key = self._get_secret_key() application.secret_key = self._get_secret_key()
kwargs = {
'bind': f'{self.bind_address}:{self.port}',
'workers': (cpu_count() * 2) + 1,
}
if self.ssl_context: WSGIApplicationWrapper(f'{__package__}.app:application', kwargs).run()
kwargs['ssl_context'] = self.ssl_context
if self.flask_args:
kwargs.update(self.flask_args)
waitress.serve(application, **kwargs)
return proc return proc

View file

@ -0,0 +1,21 @@
from gunicorn.app.wsgiapp import WSGIApplication
class WSGIApplicationWrapper(WSGIApplication):
"""
Wrapper for the Flask application into a WSGI application.
"""
def __init__(self, app_uri, options=None):
self.options = options or {}
self.app_uri = app_uri
super().__init__()
def load_config(self):
config = {
key: value
for key, value in self.options.items()
if key in self.cfg.settings and value is not None # type: ignore
}
for key, value in config.items():
self.cfg.set(key.lower(), value) # type: ignore

View file

@ -2,24 +2,25 @@
# Platypush common requirements # Platypush common requirements
### ###
pyyaml
marshmallow
marshmallow_dataclass
python-prctl
flask
websockets
redis
python-dateutil
tz
frozendict
requests
sqlalchemy
alembic alembic
bcrypt bcrypt
rsa
zeroconf>=0.27.0
paho-mqtt
websocket-client
croniter croniter
flask
frozendict
gunicorn
marshmallow
marshmallow_dataclass
paho-mqtt
python-dateutil
python-magic python-magic
waitress python-prctl
pyyaml
redis
requests
rsa
simple_websocket
sqlalchemy
tz
websocket-client
wsproto
zeroconf>=0.27.0

View file

@ -65,6 +65,7 @@ setup(
'croniter', 'croniter',
'flask', 'flask',
'frozendict', 'frozendict',
'gunicorn',
'marshmallow', 'marshmallow',
'marshmallow_dataclass', 'marshmallow_dataclass',
'python-dateutil', 'python-dateutil',
@ -75,7 +76,6 @@ setup(
'rsa', 'rsa',
'sqlalchemy', 'sqlalchemy',
'tz', 'tz',
'waitress',
'websocket-client', 'websocket-client',
'websockets', 'websockets',
'wheel', 'wheel',