[#260] Added `/ws/requests` websocket route.

This commit is contained in:
Fabio Manganiello 2023-05-09 02:40:32 +02:00
parent 7716a416e9
commit a069d23bb7
Signed by: blacklight
GPG key ID: D90FBA7F76362774
2 changed files with 101 additions and 22 deletions

View file

@ -41,9 +41,11 @@ class HttpBackend(Backend):
* To execute Platypush commands via HTTP calls. In order to do so: * To execute Platypush commands via HTTP calls. In order to do so:
* Register a user to Platypush through the web panel (usually served on ``http://host:8008/``). * Register a user to Platypush through the web panel (usually
served on ``http://host:8008/``).
* Generate a token for your user, either through the web panel (Settings -> Generate Token) or via API: * Generate a token for your user, either through the web panel
(Settings -> Generate Token) or via API:
.. code-block:: shell .. code-block:: shell
@ -66,16 +68,35 @@ class HttpBackend(Backend):
} }
}' http://host:8008/execute }' http://host:8008/execute
* To interact with your system (and control plugins and backends) through the Platypush web panel, * To interact with your system (and control plugins and backends)
by default available on ``http://host:8008/``. Any configured plugin that has an available panel through the Platypush web panel, by default available on
plugin will be automatically added to the web panel. ``http://host:8008/``. Any configured plugin that has an available
panel plugin will be automatically added to the web panel.
* To create asynchronous integrations with Platypush over websockets.
Two routes are available:
* ``/ws/events`` - Subscribe to this websocket to receive the
events generated by the application.
* ``/ws/requests`` - Subscribe to this websocket to send commands
to Platypush and receive the response asynchronously.
You will have to authenticate your connection to these websockets,
just like the ``/execute`` endpoint. In both cases, you can pass the
token either via ``Authorization: Bearer``, via the ``token`` query
string or body parameter, or leverage ``Authorization: Basic`` with
username and password (not advised), or use a valid ``session_token``
cookie from an authenticated web panel session.
* To display a fullscreen dashboard with custom widgets. * To display a fullscreen dashboard with custom widgets.
* Widgets are available as Vue.js components under ``platypush/backend/http/webapp/src/components/widgets``. * Widgets are available as Vue.js components under
``platypush/backend/http/webapp/src/components/widgets``.
* Explore their options (some may require some plugins or backends to be configured in order to work) and * Explore their options (some may require some plugins or backends
create a new dashboard template under ``~/.config/platypush/dashboards``- e.g. ``main.xml``: to be configured in order to work) and create a new dashboard
template under ``~/.config/platypush/dashboards``- e.g.
``main.xml``:
.. code-block:: xml .. code-block:: xml
@ -111,13 +132,17 @@ class HttpBackend(Backend):
</Row> </Row>
</Dashboard> </Dashboard>
* The dashboard will be accessible under ``http://host:8008/dashboard/<name>``, where ``name=main`` if for * The dashboard will be accessible under
example you stored your template under ``~/.config/platypush/dashboards/main.xml``. ``http://host:8008/dashboard/<name>``, where ``name=main`` if for
example you stored your template under
``~/.config/platypush/dashboards/main.xml``.
* To expose custom endpoints that can be called as web hooks by other applications and run some custom logic. * To expose custom endpoints that can be called as web hooks by other
All you have to do in this case is to create a hook on a applications and run some custom logic. All you have to do in this case
:class:`platypush.message.event.http.hook.WebhookEvent` with the endpoint that you want to expose and store is to create a hook on a
it under e.g. ``~/.config/platypush/scripts/hooks.py``: :class:`platypush.message.event.http.hook.WebhookEvent` with the
endpoint that you want to expose and store it under e.g.
``~/.config/platypush/scripts/hooks.py``:
.. code-block:: python .. code-block:: python
@ -142,16 +167,21 @@ class HttpBackend(Backend):
module can expose lists of routes to the main webapp through the module can expose lists of routes to the main webapp through the
``__routes__`` object (a list of Flask blueprints). ``__routes__`` object (a list of Flask blueprints).
Security: Access to the endpoints requires at least one user to be registered. Access to the endpoints is regulated Security: Access to the endpoints requires at least one user to be
in the following ways (with the exception of event hooks, whose logic is up to the user): registered. Access to the endpoints is regulated in the following ways
(with the exception of event hooks, whose logic is up to the user):
* **Simple authentication** - i.e. registered username and password. * **Simple authentication** - i.e. registered username and password.
* **JWT token** provided either over as ``Authorization: Bearer`` header or ``GET`` ``?token=<TOKEN>`` * **JWT token** provided either over as ``Authorization: Bearer``
parameter. A JWT token can be generated either through the web panel or over the ``/auth`` endpoint. header or ``GET`` ``?token=<TOKEN>`` parameter. A JWT token can be
* **Global platform token**, usually configured on the root of the ``config.yaml`` as ``token: <VALUE>``. generated either through the web panel or over the ``/auth``
It can provided either over on the ``X-Token`` header or as a ``GET`` ``?token=<TOKEN>`` parameter. endpoint.
* **Session token**, generated upon login, it can be used to authenticate requests through the ``Cookie`` header * **Global platform token**, usually configured on the root of the
(cookie name: ``session_token``). ``config.yaml`` as ``token: <VALUE>``. It can provided either over on
the ``X-Token`` header or as a ``GET`` ``?token=<TOKEN>`` parameter.
* **Session token**, generated upon login, it can be used to
authenticate requests through the ``Cookie`` header (cookie name:
``session_token``).
""" """

View file

@ -0,0 +1,49 @@
from threading import Thread, current_thread
from typing import Set
from typing_extensions import override
from platypush.backend.http.app.utils import send_message
from platypush.message.request import Request
from . import WSRoute, logger
class WSRequestsProxy(WSRoute):
"""
Websocket event proxy mapped to ``/ws/requests``.
"""
_max_concurrent_requests: int = 10
""" Maximum number of concurrent requests allowed on the same connection. """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._requests: Set[Thread] = set()
@classmethod
@override
def app_name(cls) -> str:
return 'requests'
def _handle_request(self, request: Request):
self._requests.add(current_thread())
try:
response = send_message(request, wait_for_response=True)
self.send(str(response))
finally:
self._requests.remove(current_thread())
def on_message(self, message):
if len(self._requests) > self._max_concurrent_requests:
logger.info('Too many concurrent requests on %s', self)
return
try:
msg = Request.build(message)
assert isinstance(msg, Request), f'Expected {Request}, got {type(msg)}'
except Exception as e:
logger.info('Could not build request from %s: %s', message, e)
logger.exception(e)
return
Thread(target=self._handle_request, args=(msg,)).start()