forked from platypush/platypush
[#260] Added `/ws/requests
` websocket route.
This commit is contained in:
parent
7716a416e9
commit
a069d23bb7
2 changed files with 101 additions and 22 deletions
|
@ -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``).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
49
platypush/backend/http/app/ws/requests.py
Normal file
49
platypush/backend/http/app/ws/requests.py
Normal 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()
|
Loading…
Reference in a new issue