http#

Description#

The HTTP backend is a general-purpose web server.

Example configuration:

backend.http:
    # Default HTTP listen port
    port: 8008
    # External folders that will be exposed over `/resources/<name>`
    resource_dirs:
        photos: /mnt/hd/photos
        videos: /mnt/hd/videos
        music: /mnt/hd/music

You can leverage this 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/).

    • Generate a token for your user, either through the web panel (Settings -> Generate Token) or via API:

      curl -XPOST -H 'Content-Type: application/json' -d '
        {
          "username": "$YOUR_USER",
          "password": "$YOUR_PASSWORD"
        }' http://host:8008/auth
      
    • Execute actions through the /execute endpoint:

      curl -XPOST -H 'Content-Type: application/json' -H "Authorization: Bearer $YOUR_TOKEN" -d '
        {
          "type": "request",
          "action": "tts.say",
          "args": {
            "text": "This is a test"
          }
        }' 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 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.

    • 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:

      <Dashboard>
          <!-- Display the following widgets on the same row. Each row consists of 12 columns.
               You can specify the width of each widget either through class name (e.g. col-6 means
               6 columns out of 12, e.g. half the size of the row) or inline style
               (e.g. `style="width: 50%"`). -->
          <Row>
              <!-- Show a calendar widget with the upcoming events. It requires the `calendar` plugin to
                   be enabled and configured. -->
              <Calendar class="col-6" />
      
              <!-- Show the current track and other playback info. It requires `music.mpd` plugin or any
                   other music plugin enabled. -->
              <Music class="col-3" />
      
              <!-- Show current date, time and weather.
                   It requires a `weather` plugin or backend enabled -->
              <DateTimeWeather class="col-3" />
          </Row>
      
          <!-- Display the following widgets on a second row -->
          <Row>
              <!-- Show a carousel of images from a local folder. For security reasons, the folder must be
                   explicitly exposed as an HTTP resource through the backend
                   `resource_dirs` attribute. -->
              <ImageCarousel class="col-6" img-dir="/mnt/hd/photos/carousel" />
      
              <!-- Show the news headlines parsed from a list of RSS feed and stored locally through the
                   `http.poll` backend -->
              <RssNews class="col-6" db="sqlite:////path/to/your/rss.db" />
          </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.

  • 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 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:

    from platypush import get_plugin, when
    from platypush.message.event.http.hook import WebhookEvent
    
    hook_token = 'abcdefabcdef'
    
    # Expose the hook under the /hook/lights_toggle endpoint
    @when(WebhookEvent, hook='lights_toggle')
    def lights_toggle(event, **context):
        # Do any checks on the request
        assert event.headers.get('X-Token') == hook_token, 'Unauthorized'
    
        # Run some actions
        lights = get_plugin('light.hue')
        lights.toggle()
    

Any plugin can register custom routes under platypush/backend/http/app/routes/plugins. Any additional route is managed as a Flask blueprint template and the .py 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):

  • 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).

Configuration#

backend.http:
  # [Optional]
  # Listen port for the web server (default: 8008)
  # port: 8008  # type=int

  # [Optional]
  # Address/interface to bind to (default: 0.0.0.0, accept connection from any IP)
  # bind_address: 0.0.0.0  # type=str

  # [Optional]
  # Static resources directories that will be
  # accessible through ``/resources/<path>``. It is expressed as a map
  # where the key is the relative path under ``/resources`` to expose and
  # the value is the absolute path to expose.
  # resource_dirs:   # type=Optional[Mapping[str, str]]

  # [Optional]
  # Path to the file containing the secret key that will be used by Flask
  # (default: ``~/.local/share/platypush/flask.secret.key``).
  # secret_key_file:   # type=Optional[str]

  # [Optional]
  # Number of worker processes to use (default: ``(cpu_count * 2) + 1``).
  # num_workers:   # type=Optional[int]

  # [Optional]
  # 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.
  # use_werkzeug_server: False  # type=bool

  # [Optional]
  # Reference to the bus object to be used in the backend
  # bus:   # type=Optional[platypush.bus.Bus]

  # [Optional]
  # If the backend implements a ``loop`` method, this parameter expresses how often the
  # loop should run in seconds.
  # poll_seconds:   # type=Optional[float]

Module reference#

class platypush.backend.http.HttpBackend(port: int = 8008, bind_address: str = '0.0.0.0', resource_dirs: Mapping[str, str] | None = None, secret_key_file: str | None = None, num_workers: int | None = None, use_werkzeug_server: bool = False, **kwargs)[source]#

Bases: Backend

The HTTP backend is a general-purpose web server.

Example configuration:

backend.http:
    # Default HTTP listen port
    port: 8008
    # External folders that will be exposed over `/resources/<name>`
    resource_dirs:
        photos: /mnt/hd/photos
        videos: /mnt/hd/videos
        music: /mnt/hd/music

You can leverage this 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/).

    • Generate a token for your user, either through the web panel (Settings -> Generate Token) or via API:

      curl -XPOST -H 'Content-Type: application/json' -d '
        {
          "username": "$YOUR_USER",
          "password": "$YOUR_PASSWORD"
        }' http://host:8008/auth
      
    • Execute actions through the /execute endpoint:

      curl -XPOST -H 'Content-Type: application/json' -H "Authorization: Bearer $YOUR_TOKEN" -d '
        {
          "type": "request",
          "action": "tts.say",
          "args": {
            "text": "This is a test"
          }
        }' 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 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.

    • 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:

      <Dashboard>
          <!-- Display the following widgets on the same row. Each row consists of 12 columns.
               You can specify the width of each widget either through class name (e.g. col-6 means
               6 columns out of 12, e.g. half the size of the row) or inline style
               (e.g. `style="width: 50%"`). -->
          <Row>
              <!-- Show a calendar widget with the upcoming events. It requires the `calendar` plugin to
                   be enabled and configured. -->
              <Calendar class="col-6" />
      
              <!-- Show the current track and other playback info. It requires `music.mpd` plugin or any
                   other music plugin enabled. -->
              <Music class="col-3" />
      
              <!-- Show current date, time and weather.
                   It requires a `weather` plugin or backend enabled -->
              <DateTimeWeather class="col-3" />
          </Row>
      
          <!-- Display the following widgets on a second row -->
          <Row>
              <!-- Show a carousel of images from a local folder. For security reasons, the folder must be
                   explicitly exposed as an HTTP resource through the backend
                   `resource_dirs` attribute. -->
              <ImageCarousel class="col-6" img-dir="/mnt/hd/photos/carousel" />
      
              <!-- Show the news headlines parsed from a list of RSS feed and stored locally through the
                   `http.poll` backend -->
              <RssNews class="col-6" db="sqlite:////path/to/your/rss.db" />
          </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.

  • 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 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:

    from platypush import get_plugin, when
    from platypush.message.event.http.hook import WebhookEvent
    
    hook_token = 'abcdefabcdef'
    
    # Expose the hook under the /hook/lights_toggle endpoint
    @when(WebhookEvent, hook='lights_toggle')
    def lights_toggle(event, **context):
        # Do any checks on the request
        assert event.headers.get('X-Token') == hook_token, 'Unauthorized'
    
        # Run some actions
        lights = get_plugin('light.hue')
        lights.toggle()
    

Any plugin can register custom routes under platypush/backend/http/app/routes/plugins. Any additional route is managed as a Flask blueprint template and the .py 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):

  • 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).

DEFAULT_HTTP_PORT = 8008#

The default listen port for the webserver.

__init__(port: int = 8008, bind_address: str = '0.0.0.0', resource_dirs: Mapping[str, str] | None = None, secret_key_file: str | None = None, num_workers: int | None = None, use_werkzeug_server: bool = False, **kwargs)[source]#
Parameters:
  • port – Listen port for the web server (default: 8008)

  • bind_address – Address/interface to bind to (default: 0.0.0.0, accept connection from any IP)

  • resource_dirs – Static resources directories that will be accessible through /resources/<path>. It is expressed as a map where the key is the relative path under /resources to expose and the value is the absolute path to expose.

  • secret_key_file – Path to the file containing the secret key that will be used by Flask (default: ~/.local/share/platypush/flask.secret.key).

  • num_workers – Number of worker processes to use (default: (cpu_count * 2) + 1).

  • 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.

property daemon#

A boolean value indicating whether this thread is a daemon thread.

This must be set before start() is called, otherwise RuntimeError is raised. Its initial value is inherited from the creating thread; the main thread is not a daemon thread and therefore all threads created in the main thread default to daemon = False.

The entire Python program exits when only daemon threads are left.

getName()#

Return a string used for identification purposes only.

This method is deprecated, use the name attribute instead.

property ident#

Thread identifier of this thread or None if it has not been started.

This is a nonzero integer. See the get_ident() function. Thread identifiers may be recycled when a thread exits and another thread is created. The identifier is available even after the thread has exited.

isDaemon()#

Return whether this thread is a daemon.

This method is deprecated, use the daemon attribute instead.

is_alive()#

Return whether the thread is alive.

This method returns True just before the run() method starts until just after the run() method terminates. See also the module function enumerate().

join(timeout=None)#

Wait until the thread terminates.

This blocks the calling thread until the thread whose join() method is called terminates – either normally or through an unhandled exception or until the optional timeout occurs.

When the timeout argument is present and not None, it should be a floating point number specifying a timeout for the operation in seconds (or fractions thereof). As join() always returns None, you must call is_alive() after join() to decide whether a timeout happened – if the thread is still alive, the join() call timed out.

When the timeout argument is not present or None, the operation will block until the thread terminates.

A thread can be join()ed many times.

join() raises a RuntimeError if an attempt is made to join the current thread as that would cause a deadlock. It is also an error to join() a thread before it has been started and attempts to do so raises the same exception.

property name#

A string used for identification purposes only.

It has no semantics. Multiple threads may be given the same name. The initial name is set by the constructor.

property native_id#

Native integral thread ID of this thread, or None if it has not been started.

This is a non-negative integer. See the get_native_id() function. This represents the Thread ID as reported by the kernel.

notify_web_clients(event)[source]#

Notify all the connected web clients (over websocket) of a new event

on_message(msg)#

Callback when a message is received on the backend. It parses and posts the message on the main bus. It should be called by the derived classes whenever a new message should be processed.

Parameters:

msg – Received message. It can be either a key-value dictionary, a platypush.message.Message object, or a string/byte UTF-8 encoded string

on_stop()[source]#

On backend stop

register_service(port: int | None = None, name: str | None = None, srv_type: str | None = None, srv_name: str | None = None, udp: bool = False, properties: Dict | None = None)#

Initialize the Zeroconf service configuration for this backend.

Parameters:
  • port – Service listen port (default: the backend port attribute if available, or None).

  • name – Service short name (default: backend name).

  • srv_type – Service type (default: _platypush-{name}._{proto}.local.).

  • srv_name – Full service name (default: {hostname or device_id}.{type}).

  • udp – Set to True if this is a UDP service.

  • properties

    Extra properties to be passed on the service. Default:

    {
        "name": "Platypush",
        "vendor": "Platypush",
        "version": "{platypush_version}"
    }
    

run()[source]#

Starts the backend thread. To be implemented in the derived classes if the loop method isn’t defined.

send_event(event, **kwargs)#

Send an event message on the backend.

Parameters:

event – Event to send. It can be a dict, a string/bytes UTF-8 JSON, or a platypush.message.event.Event object.

send_message(*_, **__)[source]#

Sends a platypush.message.Message to a node. To be implemented in the derived classes. By default, if the Redis backend is configured then it will try to deliver the message to other consumers through the configured Redis main queue.

Parameters:
  • msg – The message to send

  • queue_name – Send the message on a specific queue (default: the queue_name configured on the Redis backend)

send_request(request, on_response=None, response_timeout=5, **kwargs)#

Send a request message on the backend.

Parameters:
  • request – The request, either a dict, a string/bytes UTF-8 JSON, or a platypush.message.request.Request object.

  • on_response (function) – Optional callback that will be called when a response is received. If set, this method will synchronously wait for a response before exiting.

  • response_timeout (float) – If on_response is set, the backend will raise an exception if the response isn’t received within this number of seconds (default: None)

send_response(response, request, **kwargs)#

Send a response message on the backend.

Parameters:
  • response – The response, either a dict, a string/bytes UTF-8 JSON, or a platypush.message.response.Response object.

  • request – Associated request, used to set the response parameters that will link them

setDaemon(daemonic)#

Set whether this thread is a daemon.

This method is deprecated, use the .daemon property instead.

setName(name)#

Set the name string for this thread.

This method is deprecated, use the name attribute instead.

should_stop()#
Returns:

True if the backend thread should be stopped, False otherwise.

start()#

Start the thread’s activity.

It must be called at most once per thread object. It arranges for the object’s run() method to be invoked in a separate thread of control.

This method will raise a RuntimeError if called more than once on the same thread object.

stop()#

Stops the backend thread by sending a STOP event on its bus

unregister_service()#

Unregister the Zeroconf service configuration if available.

wait_stop(timeout=None) bool#

Waits for the backend thread to stop.

Parameters:

timeout – The maximum time to wait for the backend thread to stop (default: None)

Returns:

True if the backend thread has stopped, False otherwise.