Ejemplo n.º 1
0
    def send(self, message: str) -> None:
        """
        Send message across the websocket.

        :param message: Message as string
        :raise WebSocketException: When *self._auto_reconnect* is False: If a message cannot be sent and all retries
                                   have been exhausted.
        :raise WebSocketConnectionClosedException: When the websocket is not available at all.
        """
        retries = 0
        retry_delay = 0

        while True:
            retry_delay = self._backoff(retry_delay)

            try:
                with self._reader_guard:
                    if self._reader_status in [ReaderStatus.NONE]:
                        raise WebSocketConnectionClosedException('Websocket not started.')
                    elif self._reader_status in [ReaderStatus.DONE, ReaderStatus.FAILED]:
                        raise WebSocketConnectionClosedException('Websocket has exited.')
                    elif self._reader_status not in [ReaderStatus.RUNNING, ReaderStatus.RUNNING_PRELIMINARY]:
                        raise WebSocketException('Websocket not ready.')

                with self._ws_guard:
                    if not self._ws:
                        raise WebSocketConnectionClosedException('Websocket is gone.')

                    if logger.isEnabledFor(logging.DEBUG):
                        logger.debug("Sending message: %s", message)
                    else:
                        logger.info("Sending message of length %d", len(message))

                    self._ws.send(message)

                return

            except WebSocketConnectionClosedException:
                raise

            except Exception as err:
                if retries >= self.MAX_RETRIES:
                    if self._auto_reconnect:
                        retries = 0
                        logger.warning('Restarting because of error: %s', str(err))
                        self.restart()
                    else:
                        raise WebSocketException("Could not send and all retries have been exhausted.")
                else:
                    logger.warning('Retrying to send message because of error: %s', str(err))
                    retries += 1
Ejemplo n.º 2
0
def test_gateway_end_when_websocket_exception(ws_mock):
    ws_mock.recv_data.side_effect = [WebSocketException()]

    gateway = Gateway(CONNECTION_URL)

    for data in gateway:
        pytest.fail("Should receive no data")
Ejemplo n.º 3
0
async def test_send_key_websocketexception(hass, remote):
    """Testing unhandled response exception."""
    await setup_samsungtv(hass, MOCK_CONFIG)
    remote.control = Mock(side_effect=WebSocketException("Boom"))
    assert await hass.services.async_call(DOMAIN, SERVICE_VOLUME_UP,
                                          {ATTR_ENTITY_ID: ENTITY_ID}, True)
    state = hass.states.get(ENTITY_ID)
    assert state.state == STATE_ON
 def create_connection(self, *a, **kw):
     self._report_call('create_connection', *a, **kw)
     if self.fail:
         self.connected = False
         raise WebSocketException()
     else:
         self.connected = True
         return self
Ejemplo n.º 5
0
def test_gateway_closes_when_connect_errors(ws_mock):
    ws_mock.connected = False
    ws_mock.connect.side_effect = [WebSocketException()]
    gateway = Gateway(CONNECTION_URL)

    for data in gateway:
        pytest.fail("No data should be emitted")

    ws_mock.connect.assert_called_once()
    ws_mock.recv_data.assert_not_called()
    assert gateway.close_reason
    assert WebSocketException.__name__ in gateway.close_reason.reason
Ejemplo n.º 6
0
    def _check_message(self, ws: WebSocketApp, message: str) -> None:
        """
        Look for error 401. Try to reconnect with a new token when this is encountered.
        Set status to *ReaderStatus.RESTARTING* when a token is no longer valid on error code 401.
        Set status to *ReaderStatus.FAILED* when error 401 is received and the token was never valid.

        :param ws: WebSocketApp
        :param message: Incoming message as string
        """
        try:
            with self._reader_guard:
                error_message = ErrorMessage.parse(message)
                if error_message:
                    if error_message.code == 401:
                        if self._reader_status == ReaderStatus.RUNNING_PRELIMINARY:
                            raise WebSocketException(
                                "Received error message while token was never valid: " + str(error_message))
                        else:
                            self._api_handler.refresh_token()

                            # If we get here, the token has been refreshed successfully.
                            self._reconnect_delay = 0
                            self._reader_status = ReaderStatus.RESTARTING

                            logger.info("Refreshing token because of error: %s", str(error_message))
                            self._close(reason=f"{self._api_handler.user_agent} token refresh.")
                            return
                else:
                    if logger.isEnabledFor(logging.DEBUG):
                        logger.debug("Received message: %s", message)
                    else:
                        logger.info("Received message of length %d", len(message))

            self.on_message(ws, message)

            with self._reader_guard:
                # If we get here, the token is valid
                if self._reader_status not in [ReaderStatus.RUNNING, ReaderStatus.FAILED]:
                    self._reader_status = ReaderStatus.RUNNING
                    self._reconnect_delay = 0

        except Exception as err:
            self._set_error(err)
            self._close(status=STATUS_UNEXPECTED_CONDITION,
                        reason="Exception while handling incoming text message.")
Ejemplo n.º 7
0
 def run_forever(self):
     """
     run event loop for WebSocket framework.
     This loop is infinite loop and is alive during websocket is available.
     """
     if self.sock:
         raise WebSocketException("socket is already opened")
     try:
         self.sock = WebSocket()
         self.sock.connect(self.url)
         self._run_with_no_err(self.on_open)
         while True:
             data = self.sock.recv()
             if data is None:
                 break
             self._run_with_no_err(self.on_message, data)
     except Exception, e:
         self._run_with_no_err(self.on_error, e)
Ejemplo n.º 8
0
    def start(self) -> None:
        """
        Create the WebSocketApp

        :raise WebSocketException: When the creation of the WebSocketApp fails.
        """

        try:
            with self._ws_guard:
                self._ws = WebSocketApp(self._url,
                                        header={
                                            "Sec-WebSocket-Protocol":
                                                f"{self._protocol}, token-{self._api_handler.token}"
                                        },
                                        on_open=lambda ws: self._check_open(ws),
                                        on_close=lambda ws, code, reason: self._check_close(ws, code, reason),
                                        on_message=lambda ws, msg: self._check_message(ws, msg),
                                        on_error=lambda ws, _err: self._check_error(ws, _err),
                                        on_ping=lambda ws, data: ws.send(data, opcode=ABNF.OPCODE_PONG))

        except Exception as err:
            raise WebSocketException("Cannot create WebSocketApp.") from err
Ejemplo n.º 9
0
    def run_forever(self, sockopt=None, sslopt=None,
                    ping_interval=0, ping_timeout=None,
                    ping_payload="",
                    http_proxy_host=None, http_proxy_port=None,
                    http_no_proxy=None, http_proxy_auth=None,
                    skip_utf8_validation=True,
                    host=None, origin=None, dispatcher=None,
                    suppress_origin=False, proxy_type=None):
        """
        run event loop for WebSocket framework.
        This loop is infinite loop and is alive during websocket is available.
        sockopt: values for socket.setsockopt.
            sockopt must be tuple
            and each element is argument of sock.setsockopt.
        sslopt: ssl socket optional dict.
        ping_interval: automatically send "ping" command
            every specified period(second)
            if set to 0, not send automatically.
        ping_timeout: timeout(second) if the pong message is not received.
        http_proxy_host: http proxy host name.
        http_proxy_port: http proxy port. If not set, set to 80.
        http_no_proxy: host names, which doesn't use proxy.
        skip_utf8_validation: skip utf8 validation.
        host: update host header.
        origin: update origin header.
        dispatcher: customize reading data from socket.
        suppress_origin: suppress outputting origin header.

        Returns
        -------
        False if caught KeyboardInterrupt
        True if other exception was raised during a loop
        """

        if ping_timeout is not None and ping_timeout <= 0:
            ping_timeout = None
        if ping_timeout and ping_interval and ping_interval <= ping_timeout:
            raise WebSocketException("Ensure ping_interval > ping_timeout")
        if not sockopt:
            sockopt = []
        if not sslopt:
            sslopt = {}
        if self.sock:
            raise WebSocketException("socket is already opened")
        thread = None
        self.keep_running = True
        self.last_ping_tm = 0
        self.last_pong_tm = 0

        def teardown(close_frame=None):
            """
            Tears down the connection.
            If close_frame is set, we will invoke the on_close handler with the
            statusCode and reason from there.
            """
            if thread and thread.isAlive():
                event.set()
                thread.join()
            self.keep_running = False
            if self.sock:
                self.sock.close()
            close_args = self._get_close_args(
                close_frame.data if close_frame else None)
            self._callback(self.on_close, *close_args)
            self.sock = None

        try:
            self.sock = WebSocket(
                self.get_mask_key, sockopt=sockopt, sslopt=sslopt,
                fire_cont_frame=self.on_cont_message is not None,
                skip_utf8_validation=skip_utf8_validation,
                enable_multithread=True if ping_interval else False,
                sock_id=self.sock_id
            )
            self.sock.settimeout(getdefaulttimeout())
            self.sock.connect(
                self.url, header=self.header, cookie=self.cookie,
                http_proxy_host=http_proxy_host,
                http_proxy_port=http_proxy_port, http_no_proxy=http_no_proxy,
                http_proxy_auth=http_proxy_auth, subprotocols=self.subprotocols,
                host=host, origin=origin, suppress_origin=suppress_origin,
                proxy_type=proxy_type)
            if not dispatcher:
                dispatcher = self.create_dispatcher(ping_timeout)

            self._callback(self.on_open)

            if ping_interval:
                event = threading.Event()
                thread = threading.Thread(
                    target=self._send_ping, args=(ping_interval, event, ping_payload))
                thread.setDaemon(True)
                thread.start()

            def read():
                if not self.keep_running:
                    return teardown()

                op_code, frame = self.sock.recv_data_frame(True)
                if op_code == ABNF.OPCODE_CLOSE:
                    return teardown(frame)
                elif op_code == ABNF.OPCODE_PING:
                    self._callback(self.on_ping, frame.data)
                elif op_code == ABNF.OPCODE_PONG:
                    self.last_pong_tm = time.time()
                    self._callback(self.on_pong, frame.data)
                elif op_code == ABNF.OPCODE_CONT and self.on_cont_message:
                    self._callback(self.on_data, frame.data,
                                   frame.opcode, frame.fin)
                    self._callback(self.on_cont_message,
                                   frame.data, frame.fin)
                else:
                    data = frame.data
                    if six.PY3 and op_code == ABNF.OPCODE_TEXT:
                        data = data.decode("utf-8")
                    self._callback(self.on_data, data, frame.opcode, True)
                    self._callback(self.on_message, data)

                return True

            def check():
                if (ping_timeout):
                    has_timeout_expired = time.time() - self.last_ping_tm > ping_timeout
                    has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0
                    has_pong_arrived_too_late = self.last_pong_tm - self.last_ping_tm > ping_timeout

                    if (self.last_ping_tm
                            and has_timeout_expired
                            and (has_pong_not_arrived_after_last_ping or has_pong_arrived_too_late)):
                        raise WebSocketTimeoutException("ping/pong timed out")
                return True

            dispatcher.read(self.sock.sock, read, check)
        except (Exception, KeyboardInterrupt, SystemExit) as e:
            self._callback(self.on_error, e)
            if isinstance(e, SystemExit):
                # propagate SystemExit further
                raise
            teardown()
            return not isinstance(e, KeyboardInterrupt)