[#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 untrusted user: 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:
* 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
@ -66,16 +68,35 @@ class HttpBackend(Backend):
}
}' http://host:8008/execute
* To interact with your system (and control plugins and backends) through the Platypush web panel,
by default available on ``http://host:8008/``. Any configured plugin that has an available panel
plugin will be automatically added to the web panel.
* To interact with your system (and control plugins and backends)
through the Platypush web panel, by default available on
``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.
* 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
create a new dashboard template under ``~/.config/platypush/dashboards``- e.g. ``main.xml``:
* Explore their options (some may require some plugins or backends
to be configured in order to work) and create a new dashboard
template under ``~/.config/platypush/dashboards``- e.g.
``main.xml``:
.. code-block:: xml
@ -111,13 +132,17 @@ class HttpBackend(Backend):
</Row>
</Dashboard>
* The dashboard will be accessible under ``http://host:8008/dashboard/<name>``, where ``name=main`` if for
example you stored your template under ``~/.config/platypush/dashboards/main.xml``.
* The dashboard will be accessible under
``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.
All you have to do in this case is to create a hook on a
: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``:
* To expose custom endpoints that can be called as web hooks by other
applications and run some custom logic. All you have to do in this case
is to create a hook on a
: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
@ -142,16 +167,21 @@ class HttpBackend(Backend):
module can expose lists of routes to the main webapp through the
``__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
in the following ways (with the exception of event hooks, whose logic is up to the user):
Security: Access to the endpoints requires at least one user to be
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.
* **JWT token** provided either over as ``Authorization: Bearer`` header or ``GET`` ``?token=<TOKEN>``
parameter. A JWT token can be generated either through the web panel or over the ``/auth`` endpoint.
* **Global platform token**, usually configured on the root of the ``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``).
* **JWT token** provided either over as ``Authorization: Bearer``
header or ``GET`` ``?token=<TOKEN>`` parameter. A JWT token can be
generated either through the web panel or over the ``/auth``
endpoint.
* **Global platform token**, usually configured on the root of the
``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()