Beispiel #1
0
    def _exec(method, url, output='text', **kwargs):
        """ Available output types: text (default), json, binary """

        if 'username' in kwargs and 'password' in kwargs:
            kwargs['auth'] = (kwargs.pop('username'), kwargs.pop('password'))

        method = getattr(requests, method)
        response = method(url, **kwargs)
        response.raise_for_status()

        if output == 'json':
            output = response.json()
        if output == 'binary':
            output = response.content
        else:
            output = response.text

        # noinspection PyBroadException
        try:
            # If the response is a Platypush JSON, extract it
            output = Message.build(output)
        except:
            pass

        return output
Beispiel #2
0
        def on_message(client, userdata, msg):
            def response_thread(msg):
                set_thread_name('MQTTProcessor')
                response = self.get_message_response(msg)
                if not response:
                    return
                response_topic = '{}/responses/{}'.format(self.topic, msg.id)

                self.logger.info('Processing response on the MQTT topic {}: {}'.
                                format(response_topic, response))

                self.send_message(response)

            msg = msg.payload.decode('utf-8')
            try: msg = Message.build(json.loads(msg))
            except: pass
            if not msg: return

            self.logger.info('Received message on the MQTT backend: {}'.format(msg))

            try:
                self.on_message(msg)
            except Exception as e:
                self.logger.exception(e)
                return

            if isinstance(msg, Request):
                threading.Thread(target=response_thread,
                                 name='MQTTProcessor',
                                 args=(msg,)).start()
Beispiel #3
0
    def on_message(self, 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.

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

        msg = Message.build(msg)

        if not getattr(msg, 'target') or msg.target != self.device_id:
            return  # Not for me

        self.logger.debug('Message received on the {} backend: {}'.format(
            self.__class__.__name__, msg))

        if self._is_expected_response(msg):
            # Expected response, trigger the response handler
            clear_timeout()
            self._request_context['on_response'](msg)
            self.stop()
            return

        if isinstance(msg, StopEvent) and msg.targets_me():
            self.logger.info('Received STOP event on {}'.format(
                self.__class__.__name__))
            self._stop = True
        else:
            msg.backend = self  # Augment message to be able to process responses
            self.bus.post(msg)
Beispiel #4
0
    def get_message(self, queue_name=None):
        queue = queue_name or self.queue
        msg = self.redis.blpop(queue)[1].decode('utf-8')

        try:
            msg = Message.build(json.loads(msg))
        except:
            try:
                import ast
                msg = Message.build(ast.literal_eval(msg))
            except:
                try:
                    msg = json.loads(msg)
                except Exception as e:
                    self.logger.exception(e)

        return msg
Beispiel #5
0
    def send(self,
             url,
             msg,
             ssl_cert=None,
             ssl_key=None,
             ssl_cafile=None,
             ssl_capath=None,
             *args,
             **kwargs):
        """
        Sends a message to a websocket.

        :param url: Websocket URL, e.g. ws://localhost:8765 or wss://localhost:8765
        :type topic: str

        :param msg: Message to be sent. It can be a list, a dict, or a Message object

        :param ssl_cert: Path to the SSL certificate to be used, if the SSL connection requires client authentication as well (default: None)
        :type ssl_cert: str

        :param ssl_key: Path to the SSL key to be used, if the SSL connection requires client authentication as well (default: None)
        :type ssl_key: str

        :param ssl_cafile: Path to the certificate authority file if required by the SSL configuration (default: None)
        :type ssl_cafile: str

        :param ssl_capath: Path to the certificate authority directory if required by the SSL configuration (default: None)
        :type ssl_capath: str
        """
        async def send():
            websocket_args = {}
            if ssl_cert:
                websocket_args['ssl'] = get_ssl_client_context(
                    ssl_cert=ssl_cert,
                    ssl_key=ssl_key,
                    ssl_cafile=ssl_cafile,
                    ssl_capath=ssl_capath)

            async with websockets.connect(url, **websocket_args) as websocket:
                try:
                    await websocket.send(str(msg))
                except websockets.exceptions.ConnectionClosed:
                    self.logger.warning('Error on websocket {}: {}'.format(
                        url, e))

        try:
            msg = json.dumps(msg)
        except:
            pass

        try:
            msg = Message.build(json.loads(msg))
        except:
            pass

        loop = get_or_create_event_loop()
        loop.run_until_complete(send())
Beispiel #6
0
def get_message_response(msg):
    redis = Redis(**bus().redis_args)
    response = redis.blpop(get_redis_queue_name_by_message(msg), timeout=60)
    if response and len(response) > 1:
        response = Message.build(response[1])
    else:
        response = None

    return response
Beispiel #7
0
    def get_message(self, queue_name=None):
        queue = queue_name or self.queue
        data = self.redis.blpop(queue, timeout=1)
        if data is None:
            return

        msg = data[1].decode()
        try:
            msg = Message.build(msg)
        except Exception as e:
            self.logger.debug(str(e))
            try:
                import ast
                msg = Message.build(ast.literal_eval(msg))
            except Exception as ee:
                self.logger.debug(str(ee))
                try:
                    msg = json.loads(msg)
                except Exception as eee:
                    self.logger.exception(eee)

        return msg
Beispiel #8
0
        def _f():
            processed_bytes = 0
            open_brackets = 0
            msg = b''
            prev_ch = None
            redis = self._get_redis()

            while True:
                if processed_bytes > self._MAX_REQ_SIZE:
                    self.logger.warning(
                        'Ignoring message longer than {} bytes from {}'.format(
                            self._MAX_REQ_SIZE, address[0]))
                    return

                ch = sock.recv(1)
                processed_bytes += 1

                if ch == b'':
                    break

                if ch == b'{' and prev_ch != b'\\':
                    open_brackets += 1

                if not open_brackets:
                    continue

                msg += ch

                if ch == b'}' and prev_ch != b'\\':
                    open_brackets -= 1

                if not open_brackets:
                    break

                prev_ch = ch

            if msg == b'':
                return

            msg = Message.build(msg)
            self.logger.info('Received request from {}: {}'.format(
                msg, address[0]))
            self.on_message(msg)

            response = self.get_message_response(msg)
            self.logger.info(
                'Processing response on the TCP backend: {}'.format(response))

            if response:
                sock.send(str(response).encode())
Beispiel #9
0
    def get(self):
        """ Reads one message from the Redis queue """
        try:
            if self.should_stop():
                return

            msg = self.redis.blpop(self.redis_queue, timeout=1)
            if not msg or msg[1] is None:
                return

            msg = msg[1].decode('utf-8')
            return Message.build(msg)
        except Exception as e:
            logger.exception(e)
Beispiel #10
0
    def get_message_response(self, msg):
        try:
            redis = self._get_redis()
            response = redis.blpop(get_redis_queue_name_by_message(msg),
                                   timeout=60)
            if response and len(response) > 1:
                response = Message.build(response[1])
            else:
                response = None

            return response
        except Exception as e:
            self.logger.error(
                'Error while processing response to {}: {}'.format(
                    msg, str(e)))
Beispiel #11
0
    def _on_record(self, record):
        if record.topic != self.topic: return
        msg = record.value.decode('utf-8')
        is_platypush_message = False

        try:
            msg = Message.build(msg)
            is_platypush_message = True
        except:
            pass

        self.logger.info('Received message on Kafka backend: {}'.format(msg))

        if is_platypush_message:
            self.on_message(msg)
        else:
            self.on_message(KafkaMessageEvent(msg=msg))
Beispiel #12
0
def send_message(msg, wait_for_response=True):
    msg = Message.build(msg)

    if isinstance(msg, Request):
        msg.origin = 'http'

    if Config.get('token'):
        msg.token = Config.get('token')

    bus().post(msg)

    if isinstance(msg, Request) and wait_for_response:
        response = get_message_response(msg)
        logger().debug(
            'Processing response on the HTTP backend: {}'.format(response))

        return response
Beispiel #13
0
    def get(self):
        """ Reads one message from the Redis queue """
        msg = None

        try:
            msg = self.redis.blpop(self.redis_queue)
            if not msg or msg[1] is None:
                return

            msg = msg[1].decode('utf-8')
            try:
                msg = json.loads(msg)
            except json.decoder.JSONDecodeError:
                msg = ast.literal_eval(msg)

            msg = Message.build(msg)
        except Exception as e:
            logger.exception(e)

        return msg
Beispiel #14
0
    def send_request(self):
        set_timeout(seconds=self.timeout,
                    on_timeout=self.on_timeout('Receiver response timed out'))

        response = requests.post(
            u'http://localhost:8123/execute',
            json  = {
                'type': 'request',
                'target': Config.get('device_id'),
                'action': 'shell.exec',
                'args': { 'cmd':'echo ping' }
            }
        )

        clear_timeout()

        response = Message.build(response.json())
        self.assertTrue(isinstance(response, Response))
        self.assertEqual(response.output.strip(), 'ping')
        self.receiver.stop_app()
Beispiel #15
0
        def handler(_, __, msg):
            # noinspection PyShadowingNames
            def response_thread(msg):
                set_thread_name('MQTTProcessor')
                response = self.get_message_response(msg)
                if not response:
                    return
                response_topic = '{}/responses/{}'.format(self.topic, msg.id)

                self.logger.info(
                    'Processing response on the MQTT topic {}: {}'.format(
                        response_topic, response))

                self.send_message(response, topic=response_topic)

            msg = msg.payload.decode('utf-8')
            # noinspection PyBroadException
            try:
                msg = json.loads(msg)
                msg = Message.build(msg)
            except Exception as e:
                self.logger.debug(str(e))

            if not msg:
                return

            self.logger.info(
                'Received message on the MQTT backend: {}'.format(msg))

            try:
                self.on_message(msg)
            except Exception as e:
                self.logger.exception(e)
                return

            if isinstance(msg, Request):
                threading.Thread(target=response_thread,
                                 name='MQTTProcessor',
                                 args=(msg, )).start()
Beispiel #16
0
        async def serve_client(websocket, path):
            self.active_websockets.add(websocket)
            self.logger.debug('New websocket connection from {}'.format(
                websocket.remote_address[0]))

            try:
                while True:
                    if self.client_timeout:
                        msg = await asyncio.wait_for(
                            websocket.recv(), timeout=self.client_timeout)
                    else:
                        msg = await websocket.recv()

                    msg = Message.build(msg)
                    self.logger.info('Received message from {}: {}'.format(
                        websocket.remote_address[0], msg))

                    self.on_message(msg)

                    if isinstance(msg, Request):
                        response = self.get_message_response(msg) or Response()
                        self.logger.info(
                            'Processing response on the websocket backend: {}'.
                            format(response))

                        await websocket.send(str(response))

            except websockets.exceptions.ConnectionClosed as e:
                self.active_websockets.remove(websocket)
                self.logger.debug(
                    'Websocket client {} closed connection'.format(
                        websocket.remote_address[0]))
            except asyncio.TimeoutError as e:
                self.active_websockets.remove(websocket)
                self.logger.debug(
                    'Websocket connection to {} timed out'.format(
                        websocket.remote_address[0]))
            except Exception as e:
                self.logger.exception(e)
Beispiel #17
0
    def _should_skip_last_received_msg(self, msg):
        if not isinstance(msg, dict):
            return True  # We received something weird

        is_duplicate = False
        last_msg = self._last_received_msg[msg['type']]

        if last_msg:
            msg = Message.parse(msg)
            if str(msg) == str(last_msg['body']) \
                    and time.time() - last_msg['time'] <= 2:
                # Duplicate message sent on the Pushbullet socket within
                # two seconds, ignore it
                logging.debug(
                    'Ignoring duplicate message received on the socket')
                is_duplicate = True

        self._last_received_msg[msg['type']] = {
            'body': msg,
            'time': time.time()
        }

        return is_duplicate
Beispiel #18
0
    def send_message(self, topic, msg, host=None, port=1883, tls_cafile=None,
                     tls_certfile=None, tls_keyfile=None,
                     tls_version=None, tls_ciphers=None, username=None,
                     password=None, *args, **kwargs):
        """
        Sends a message to a topic/channel.

        :param topic: Topic/channel where the message will be delivered
        :type topic: str

        :param msg: Message to be sent. It can be a list, a dict, or a Message object

        :param host: MQTT broker hostname/IP
        :type host: str

        :param port: MQTT broker port (default: 1883)
        :type port: int

        :param tls_cafile: If TLS/SSL is enabled on the MQTT server and the certificate requires a certificate authority to authenticate it, `ssl_cafile` will point to the provided ca.crt file (default: None)
        :type tls_cafile: str

        :param tls_certfile: If TLS/SSL is enabled on the MQTT server and a client certificate it required, specify it here (default: None)
        :type tls_certfile: str

        :param tls_keyfile: If TLS/SSL is enabled on the MQTT server and a client certificate key it required, specify it here (default: None)
        :type tls_keyfile: str

        :param tls_version: If TLS/SSL is enabled on the MQTT server and it requires a certain TLS version, specify it here (default: None)
        :type tls_version: str

        :param tls_ciphers: If TLS/SSL is enabled on the MQTT server and an explicit list of supported ciphers is required, specify it here (default: None)
        :type tls_ciphers: str

        :param username: Specify it if the MQTT server requires authentication (default: None)
        :type username: str

        :param password: Specify it if the MQTT server requires authentication (default: None)
        :type password: str
        """

        import paho.mqtt.publish as publisher

        if not host and not self.host:
            raise RuntimeError('No host specified and no default host configured')

        publisher_args = {
            'hostname': host or self.host,
            'port': port or self.port,
        }

        if host:
            if username and password:
                publisher_args['auth'] = {
                    'username': username,
                    'password': password,
                }
        else:
            if self.username and self.password:
                publisher_args['auth'] = {
                    'username': username,
                    'password': password,
                }

        if host:
            if tls_cafile:
                publisher_args['tls'] = { 'ca_certs': tls_cafile }
                if tls_certfile:
                    publisher_args['tls']['certfile'] = tls_certfile
                if tls_keyfile:
                    publisher_args['tls']['keyfile'] = tls_keyfile
                if tls_version:
                    publisher_args['tls']['tls_version'] = tls_version
                if tls_ciphers:
                    publisher_args['tls']['ciphers'] = tls_ciphers
        else:
            if self.tls_cafile:
                publisher_args['tls'] = { 'ca_certs': self.tls_cafile }
                if self.tls_certfile:
                    publisher_args['tls']['certfile'] = self.tls_certfile
                if self.tls_keyfile:
                    publisher_args['tls']['keyfile'] = self.tls_keyfile
                if self.tls_version:
                    publisher_args['tls']['tls_version'] = self.tls_version
                if self.tls_ciphers:
                    publisher_args['tls']['ciphers'] = self.tls_ciphers

        try: msg = json.dumps(msg)
        except: pass

        try: msg = Message.build(json.loads(msg))
        except: pass

        publisher.single(topic, str(msg), **publisher_args)
Beispiel #19
0
    def _get_next_message(self):
        fifo = self.response_fifo if self._request_context else self.request_fifo
        with open(fifo, 'rb', 0) as f:
            msg = f.readline()

        return Message.build(msg) if len(msg) else None
Beispiel #20
0
 def _parse_msg(cls, msg: Union[dict, list]):
     import json
     return Message.build(json.loads(json.dumps(msg)))
Beispiel #21
0
 def _parse_msg(cls, msg: Union[str, dict]) -> dict:
     return Message.build(json.loads(json.dumps(msg)))
Beispiel #22
0
    def publish(self,
                topic: str,
                msg: Any,
                host: Optional[str] = None,
                port: int = 1883,
                reply_topic: Optional[str] = None,
                timeout: int = 60,
                tls_cafile: Optional[str] = None,
                tls_certfile: Optional[str] = None,
                tls_keyfile: Optional[str] = None,
                tls_version: Optional[str] = None,
                tls_ciphers: Optional[str] = None,
                username: Optional[str] = None,
                password: Optional[str] = None):
        """
        Sends a message to a topic.

        :param topic: Topic/channel where the message will be delivered
        :param msg: Message to be sent. It can be a list, a dict, or a Message object.
        :param host: MQTT broker hostname/IP.
        :param port: MQTT broker port (default: 1883).
        :param reply_topic: If a ``reply_topic`` is specified, then the action will wait for a response on this topic.
        :param timeout: If ``reply_topic`` is set, use this parameter to specify the maximum amount of time to
            wait for a response (default: 60 seconds).
        :param tls_cafile: If TLS/SSL is enabled on the MQTT server and the certificate requires a certificate authority
            to authenticate it, `ssl_cafile` will point to the provided ca.crt file (default: None).
        :param tls_certfile: If TLS/SSL is enabled on the MQTT server and a client certificate it required, specify it
            here (default: None).
        :param tls_keyfile: If TLS/SSL is enabled on the MQTT server and a client certificate key it required, specify
            it here (default: None).
        :param tls_version: If TLS/SSL is enabled on the MQTT server and it requires a certain TLS version, specify it
            here (default: None).
        :param tls_ciphers: If TLS/SSL is enabled on the MQTT server and an explicit list of supported ciphers is
            required, specify it here (default: None).
        :param username: Specify it if the MQTT server requires authentication (default: None).
        :param password: Specify it if the MQTT server requires authentication (default: None).
        """
        from paho.mqtt.client import Client

        if not host and not self.host:
            raise RuntimeError(
                'No host specified and no default host configured')

        if not host:
            tls_cafile = self.tls_cafile
            tls_certfile = self.tls_certfile
            tls_keyfile = self.tls_keyfile
            tls_version = self.tls_version
            tls_ciphers = self.tls_ciphers
            username = self.username
            password = self.password

        client = Client()

        if username and password:
            client.username_pw_set(username, password)
        if tls_cafile:
            client.tls_set(ca_certs=tls_cafile,
                           certfile=tls_certfile,
                           keyfile=tls_keyfile,
                           tls_version=tls_version,
                           ciphers=tls_ciphers)

        # Try to parse it as a platypush message or dump it to JSON from a dict/list
        if isinstance(msg, dict) or isinstance(msg, list):
            msg = json.dumps(msg)

            # noinspection PyBroadException
            try:
                msg = Message.build(json.loads(msg))
            except:
                pass

        client.connect(host, port, keepalive=timeout)
        response_buffer = io.BytesIO()

        try:
            response_received = threading.Event()

            if reply_topic:
                client.on_message = self._response_callback(
                    reply_topic=reply_topic,
                    event=response_received,
                    buffer=response_buffer)
                client.subscribe(reply_topic)

            client.publish(topic, str(msg))
            if not reply_topic:
                return

            client.loop_start()
            ok = response_received.wait(timeout=timeout)
            if not ok:
                raise TimeoutError('Response timed out')
            return response_buffer.getvalue()
        finally:
            response_buffer.close()

            # noinspection PyBroadException
            try:
                client.loop_stop()
            except:
                pass

            client.disconnect()
Beispiel #23
0
def parse_response(response):
    response = Message.build(response.json())
    assert isinstance(response,
                      Response), 'Expected Response type, got {}'.format(
                          response.__class__.__name__)
    return response
Beispiel #24
0
    def publish(self,
                topic: str,
                msg: Any,
                host: Optional[str] = None,
                port: Optional[int] = None,
                reply_topic: Optional[str] = None,
                timeout: int = 60,
                tls_cafile: Optional[str] = None,
                tls_certfile: Optional[str] = None,
                tls_keyfile: Optional[str] = None,
                tls_version: Optional[str] = None,
                tls_ciphers: Optional[str] = None,
                tls_insecure: Optional[bool] = None,
                username: Optional[str] = None,
                password: Optional[str] = None):
        """
        Sends a message to a topic.

        :param topic: Topic/channel where the message will be delivered
        :param msg: Message to be sent. It can be a list, a dict, or a Message object.
        :param host: MQTT broker hostname/IP (default: default host configured on the plugin).
        :param port: MQTT broker port (default: default port configured on the plugin).
        :param reply_topic: If a ``reply_topic`` is specified, then the action will wait for a response on this topic.
        :param timeout: If ``reply_topic`` is set, use this parameter to specify the maximum amount of time to
            wait for a response (default: 60 seconds).
        :param tls_cafile: If TLS/SSL is enabled on the MQTT server and the certificate requires a certificate authority
            to authenticate it, `ssl_cafile` will point to the provided ca.crt file (default: None).
        :param tls_certfile: If TLS/SSL is enabled on the MQTT server and a client certificate it required, specify it
            here (default: None).
        :param tls_keyfile: If TLS/SSL is enabled on the MQTT server and a client certificate key it required, specify
            it here (default: None).
        :param tls_version: If TLS/SSL is enabled on the MQTT server and it requires a certain TLS version, specify it
            here (default: None). Supported versions: ``tls`` (automatic), ``tlsv1``, ``tlsv1.1``, ``tlsv1.2``.
        :param tls_insecure: Set to True to ignore TLS insecure warnings (default: False).
        :param tls_ciphers: If TLS/SSL is enabled on the MQTT server and an explicit list of supported ciphers is
            required, specify it here (default: None).
        :param username: Specify it if the MQTT server requires authentication (default: None).
        :param password: Specify it if the MQTT server requires authentication (default: None).
        """
        response_buffer = io.BytesIO()
        client = None

        try:
            # Try to parse it as a platypush message or dump it to JSON from a dict/list
            if isinstance(msg, (dict, list)):
                msg = json.dumps(msg)

                try:
                    msg = Message.build(json.loads(msg))
                except Exception as e:
                    self.logger.debug(f'Not a valid JSON: {str(e)}')

            host = host or self.host
            port = port or self.port or 1883
            assert host, 'No host specified'

            client = self._get_client(tls_cafile=tls_cafile,
                                      tls_certfile=tls_certfile,
                                      tls_keyfile=tls_keyfile,
                                      tls_version=tls_version,
                                      tls_ciphers=tls_ciphers,
                                      tls_insecure=tls_insecure,
                                      username=username,
                                      password=password)

            client.connect(host, port, keepalive=timeout)
            response_received = threading.Event()

            if reply_topic:
                client.on_message = self._response_callback(
                    reply_topic=reply_topic,
                    event=response_received,
                    buffer=response_buffer)
                client.subscribe(reply_topic)

            client.publish(topic, str(msg))
            if not reply_topic:
                return

            client.loop_start()
            ok = response_received.wait(timeout=timeout)
            if not ok:
                raise TimeoutError('Response timed out')
            return response_buffer.getvalue()
        finally:
            response_buffer.close()

            if client:
                try:
                    client.loop_stop()
                except Exception as e:
                    self.logger.warning(f'Could not stop client loop: {e}')

                client.disconnect()